BackgroundWorker problems

Ok.....I have a while loop that executes an increment on a counter for a couple thousand times. I want to be able to show the progress on the counter using a progressbar. Now, I am executing the while loop in the DoWork method of the background worker and updating the progressbar in the ProgressChanged method and when i call RunWorkerAsync then things go wrong. My problem is that the progressBar updates properly and the count increments...however my form's UI is unresponsive...it registers click events on the cancel button but it waits until the backgroundworker completes before it actually does what it has to do. If i introduce a Thread.Sleep(1) inside the while loop the form responds immediately to all events...

Can anyone help me to change my code so that my UI responds and updates itself while the backgroundworker is doing its work without the Thread.Sleep() in it

The code is something like this: NB.: the progressbar maximum is 10000

private void startButton_Click(object sender, EventArgs e) {

count = 0;

backgroundWorker1.RunWorkerAsync();

}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {

while (count < 10000) {

if (((BackgroundWorker)sender).CancellationPending)

return;

else {

count++;

//Thread.Sleep(1);

((BackgroundWorker)sender).ReportProgress(count);

}

}

}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {

progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = e.ProgressPercentage; }));

}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs ) {

Console.WriteLine("Finished");

}



Answer this question

BackgroundWorker problems

  • hatem elshenawy

    Sorry, I should have noticed this sooner.  You're sending progress for each incrementation of count.  In this example the time span between iterations will be a fraction of a millisecond.  The application message pump just can't keep up with that amount of data.  The progress bar is updating because setting its Value property pushes that to the beginning of the queue leaving no time for the next item in the queue.

    Depending on the size of your progress bar, there's no discernible difference between progress increments of less than about 5%.  I would suggest updating progress for each 5% block of work done.  In your example:


    private void startButton_Click(object sender, EventArgs e)
    {
     backgroundWorker1.RunWorkerAsync(0);
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
     BackgroundWorker backgroundWorker = sender as BackgroundWorker;
     System.Diagnostics.Debug.Assert(e.Argument is Int32);
     Int32 count = (Int32)e.Argument;
     const int iterations = 10000000;
     const int fivePercent = iterations / 20;
     while (count < iterations)
     {
      if (backgroundWorker.CancellationPending)
      {
       return;
      }
      count++;
      // report progress every 5%
      if(0 == (count % fivePercent))
      {
       backgroundWorker.ReportProgress((count * 100) / iterations);
       System.Diagnostics.Debug.WriteLine(count);
      }
     }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
     progressBar1.Value = e.ProgressPercentage;
    }

     
    In my above example you may notice that I've passed the start count value as a parameter of RunWorkerAsync(), I do this so the DoWork handler makes no instance variable access to the class.  This avoids accidentally accessing control instance values and causing cross-thread messaging problems.  Usually I go so far as to make backgroundWorker1_DoWork() static; but that causes issues with the Forms Designer.



  • Michael Koltachev

    I'm not sure how .NET implements marshalling events to another thread but if it is done the old COM way, it involves the message loop in the GUI thread. That would mean that your GUI thread is pumping messages like crazy, potentially crowding out the normal GUI update messages.

    Inherently, thread-to-thread marshalling is expensive and slow because it requires a CPU context switch, something that doesn't normally happen more than once every 15 to 45 msec or so. Unless you yield the quantum explicitly by blocking, sleeping or pumping messages. Although the API documentation states otherwise, I've noticed before that Sleep(0) doesn't cause a context switch on XP.

    Generally, limit the rate of events in a background worker thread to avoid these issues.



  • beelzeboris

    Sorry...I had left the .Sleep in my code when testing.

    Replacing Sleep with Application.Doevents()....helps...but is inherently evil.



  • wiertmir

    It should be 100%, there's nothing you do inside the thread that would cause the thread to block or sleep. A background worker thread has the same "priviledges" as any other thread to use the CPU as it sees fit. They are intended to do something that takes a lot of time to complete, not to do something that takes a lot of CPU cycles. It that makes your other programs and GUI slow, you can lower the thread's priority with CurrentThread.Priority = ThreadPriority.BelowNormal



  • Jesper.DK

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)

    {

    this.progressBar1.Value = e.ProgressPercentage;

    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

    {

    while (count < 10000)

    {

    if (backgroundWorker1.CancellationPending)

    return;

    else

    {

    count++;

    Console.WriteLine(count);

    backgroundWorker1.ReportProgress(count);

    }

    }

    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)

    {

    this.progressBar1.Value = e.ProgressPercentage;

    }



  • Xanthius

    Thanks but I have already tried that implementation before....the Console.WriteLine(count) slows down the process but that only works when debugging. If that is used without debugging then the same UI problem occurs. So far only Thread.Sleep(someValue) works with and without debugging but I do not want to slow down my implementation because the future use of this implementation is time critical.

  • Dave Rollins

    I ultimately believe that there is no way for me to allow the threads to operate as fast as they possibly can without blocking them for a finite amount of time to allow processing...I've checked my CPU usage while running my code and it gets maxed out during the processing. However, if I block my backround thread for a specific period of time for each iteration then the CPU usage is less than 8%. Blocking my thread is not an option I would like to utilise just yet.

  • Primillo

    The above code runs fine...compiled or not

    Without the Console.Writeline...its just to fast to show a cancelation....stopping a thread does not happen instantly..



  • Lalaa

    You don't need to use Invoke() in the ProgressChanged event handler. Both ProgressChanged and RunWorkerCompleted events are called on the thread that called BackgroundWorker.RunWorkerAsync. RunWorkerAsync uses the new .NET class AsyncOperationManager to ensure the proper thread context.

    The only ill side-effect from using Invoke() would be performance.

    If your background thread is running full-tilt and using most of the CPU then your GUI thread won't have CPU to refresh itself. You could try adding a Sleep(0) to see if that relinquishes enough time to the GUI thread, which is a little better than calling Sleep(1). Alternatively, you could try dropping the priority of the background worker thread. In the DoWork handler, something like System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal.



  • Bruce Williams

    Sorry....both Sleep(0) and Application.DoEvents() don't work. I understand that a thread can utilise alot of CPU but the theory is that threads allow processes to seemingly run parallel to each other....in this case some sort of CPU intensive method doing work and the main thread updating the UI. The mere fact that the progressbar and the count are both updating properly says that both threads are working "simultaneously" but I dont know why it is only the progressbar on the main thread is updating even when i update the other controls on the form.  Even changing the thread priority to the lowest doesn't work.

  • BackgroundWorker problems