Search code examples
c#backgroundworker

Backgroundworker stays busy


In my app I have a picturebox and 2 buttons("Yes" and "No"). Yes adds 1 to the resultlist, No adds 0 and both go to the next picture. Now I need to implement a timer in the app which makes the picture go to the next if no answer supplied. I thought to use a backgroundworker for it.

Code below switches the pictures fine when I don't click a button. Clicking a button freezes the UI because the backgroundworker stays "Busy". I do understand that CancelAsync does not stop the backgroundworker immediatelly, but the return-statement in DoWork is actually hit.

So my question here is why does the backgroundworker stay busy or am I totally going the wrong way here?

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Counter = 0;

        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += _backgroundWorker_DoWork;
        _backgroundWorker.WorkerSupportsCancellation = true;
        _backgroundWorker.RunWorkerAsync();
    }

    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgw = sender as BackgroundWorker;

        GoToNextItem(); //Show next picture
        while (!bgw.CancellationPending) 
        {
            _getNext = false;
            Stopwatch sw = Stopwatch.StartNew();

            //Wait interval-time
            while (!_getNext)
            {
                 if ((sw.ElapsedMilliseconds > Test.Interval * 1000) && !bgw.CancellationPending)
                {
                    _getNext = true;
                }
                if (bgw.CancellationPending)
                {
                    e.Cancel = true;
                    return; //Breakpoint is hit here
                }
            }
            if (_getNext)
            {
               Result.Add(0);
                GoToNextItem();
            }
        }
        e.Cancel = true;
    }

    private void btnNo_Click(object sender, EventArgs e)
    {
        _backgroundWorker.CancelAsync();
        Result.Add(0);

        while (_backgroundWorker.IsBusy)
        {
        _backgroundWorker.CancelAsync();
            System.Threading.Thread.Sleep(20);
        }
        _backgroundWorker.RunWorkerAsync();
    }

    private void btnYes_Click(object sender, EventArgs e)
    {
        _backgroundWorker.CancelAsync();
        Result.Add(1);

        while (_backgroundWorker.IsBusy) //Stays busy ==> UI freezes here
        {
            _backgroundWorker.CancelAsync();
            System.Threading.Thread.Sleep(20);
        }
        _backgroundWorker.RunWorkerAsync();
    }

Edit

Changed the code by using a Timer as suggested by @Servy. For more elaboration about the backgroundworker-question, read the comments of the accepted answer.


Solution

  • You should just use a System.Windows.Forms.Timer for this.

    private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
    public Form1()
    {
        InitializeComponent();
    
        timer.Interval = 5000;
        timer.Tick += timer_Tick;
        timer.Start();
    }
    
    private void timer_Tick(object sender, EventArgs e)
    {
        //runs in UI thread; code to go to next picture goes here
    }
    
    private void btnYes_Click(object sender, EventArgs e)
    {
        timer.Start();
    }
    private void btnNo_Click(object sender, EventArgs e)
    {
        timer.Start();
    }
    

    You'll want to call Start in the yes and no button clicks as well because it resets the timer, that way you'll go back to the start of your countdown for the next picture.

    You can call Stop anytime you want it to stop firing.

    As for why your current code freezes the UI, it's because your click event handlers run in the UI thread, and they are calling Sleep while waiting on the background workers. I wouldn't suggest trying to fix that approach, you should use a timer, but if you did want to you would need to attach an event handler to the completed/canceled event of the background worker and do everything that you are currently doing after the "wait until BGW is ready" in those other handlers rather than waiting in the UI thread.