10

Consider the following code:

private static BackgroundWorker bg = new BackgroundWorker();

static void Main(string[] args) {
  bg.DoWork += bg_DoWork;
  bg.ProgressChanged += bg_ProgressChanged;
  bg.WorkerReportsProgress = true;
  bg.RunWorkerAsync();

  Thread.Sleep(10000);
}

static void bg_ProgressChanged(object sender, ProgressChangedEventArgs e) {
  Console.WriteLine(e.ProgressPercentage);
  Thread.Sleep(100);
  Console.WriteLine(e.ProgressPercentage);
}

static void bg_DoWork(object sender, DoWorkEventArgs e) {
  for (int i = 0; i < 10; i++) {
    bg.ReportProgress(i);
  }
}

When run I get the following output:

0 1 1 2 0 3 2 3 5 4 4 6 5 7 7 8 6 9 8 9

I understand that the issue is a race condition between the threads that BackgroundWorker starts for each call to ReportProgress.

How can I make sure that the whole body of each bg_ProgressChanged gets executed in the order I have called them? That is I would like to get

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

as a result.

Andris
  • 1,948
  • 4
  • 13
  • 23
  • 3
    BackgroundWorker isn't really meant to be used without a UI thread. – SLaks Nov 07 '11 at 16:40
  • Prefer to use Ist parameter of ReportProgress in the increments and not the userState parameter. Suggest you to please look at this MSDN link for reporting the progress: http://msdn.microsoft.com/en-us/library/a3zbdb1t.aspx – S2S2 Nov 07 '11 at 16:48
  • Try to wrap the ReportProgress method call in a lock(_objectLock) block e.g. lock(_lockObject) { bg.ReportProgress(i); } – S2S2 Nov 07 '11 at 17:01
  • @Vijay: That won't make any difference at all; those calls run on the same thread. – SLaks Nov 07 '11 at 17:20
  • @Andris: Why are you using a BackgroundWorker in the first place? – SLaks Nov 07 '11 at 17:20
  • @SLaks: My original problem was in a Windows Form application. I just created the sample in a Console Application because I did not know that it would make a difference. – Andris Nov 07 '11 at 17:25

2 Answers2

20

BackgroundWorker raises ProgressChanged events on the current SynchronizationContext of the thread that called RunWorkerAsync().

The default SynchronizationContext runs callbacks on the ThreadPool without any synchronization.

If you use BackgroundWorker in a UI application (WPF or WinForms), it will use that UI platform's SynchronizationContext, which will execute callbacks in order.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • I have created the sample as a console application with target .NET Framework 3.5. I have changed the output type to Windows application an it worked as you have described. – Andris Nov 07 '11 at 16:48
  • My issue is that I would like the whole body of the ProgressChanged event to execute in the order I have called them. I have update the question to reflect better what I would like to do. – Andris Nov 07 '11 at 16:54
  • @Andris: Under a UI SynchronizationContext, they will execute in order. – SLaks Nov 07 '11 at 16:55
  • @SLaks: As I said I have changed the application output type to a Windows application, but please not that in the output in my revised example the first 5 comes before the first 4, so the call with parameter 5 was executed before the one with parameter 4. Isn't changing the output type enough? I'm not sure how to set the SynchronizationContext to a "UI SynchronizationContext".I'm not familiar with the topic. – Andris Nov 07 '11 at 17:15
  • 3
    You need to run a UI thread by calling `Application.Run()`. (this is a blocking call). The project type doesn't matter at all. However, if you're not doing UI, you probably shouldn't be using a BackgroundWorker at all. – SLaks Nov 07 '11 at 17:20
  • @SLaks: Thank you. Moving my code into the Load event handler of a form and running `Application.Run()` from Main had the same effect as my answer bellow. So my initial assumption that each `ProgressChanged` event runs on separate threads in a WinForm application was wrong. They all execute on the UI thread and though run neatly one after the other. – Andris Nov 07 '11 at 17:36
  • Correct. This is done by WindowsFormsSynchronizationContext.Post and the WinForms message loop. – SLaks Nov 07 '11 at 17:36
  • Why do you think [this tutorial is using `Thread.Sleep()`](http://www.codeproject.com/Articles/99143/BackgroundWorker-Class-Sample-for-Beginners)? Doesn't make any sense to me, when we have ProgressChanged()... however when I implemented solely on BGWorker functions, I realized ProgressChanged() just doesn't seem to fire. – bonCodigo Jul 01 '14 at 11:54
  • @bonCodigo: It's using `Thread.Sleep()` to simulate work. `ProgressChanged` will only fire if you call `ReportProgress()`. – SLaks Jul 01 '14 at 13:04
-1

Do not use this solution!!! May lead to deadlocks as SLaks has pointed it out.

I seem to have stumbled upon an answer. I changed the code the following way:

   [MethodImpl(MethodImplOptions.Synchronized)]
   static void bg_ProgressChanged(object sender, ProgressChangedEventArgs e) {
     Console.WriteLine(e.ProgressPercentage);
     Thread.Sleep(100);
     Console.WriteLine(e.ProgressPercentage);
   }

and now I get the output I want:

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

Andris
  • 1,948
  • 4
  • 13
  • 23
  • In a UI application, this will make no difference, since the callbacks will only run on the UI thread. – SLaks Nov 07 '11 at 17:26
  • 1
    Note that this can cause deadlocks. `[MethodImpl(MethodImplOptions.Synchronized)]` is equivalent to `lock(this)` and should not be used. – SLaks Nov 07 '11 at 17:26