30

My C# application has several background workers. Sometimes one background worker will fire off another. When the first background worker completes and the RunWorkerCompleted event is fired, on which thread will that event fire, the UI or the first background worker from which RunWorkerAsync was called? I am using Microsoft Visual C# 2008 Express Edition. Any thoughts or suggestions you may have would be appreciated. Thanks.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Jim Fell
  • 13,750
  • 36
  • 127
  • 202

3 Answers3

66

If the BackgroundWorker was created from the UI thread, then the RunWorkerCompleted event will also be raised on the UI thread.

If it was created from a background thread, the event will be raised on an undefined background thread (not necessarily the same thread, unless you're using a custom SynchronizationContext).

Interestingly, this doesn't seem to be all that well-documented on MSDN. The best reference I was able to find was here:

The preferred way to implement multithreading in your application is to use the BackgroundWorker component. The BackgroundWorker component uses an event-driven model for multithreading. The background thread runs your DoWork event handler, and the thread that creates your controls runs your ProgressChanged and RunWorkerCompleted event handlers. You can call your controls from your ProgressChanged and RunWorkerCompleted event handlers.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • Would this be where the control was instantiated? All of my background workers are instantiated (presumably) from the UI thread in the default `InitializeComponent()` function. – Jim Fell May 10 '10 at 22:19
  • @Jim: Yes, slightly poor wording on the original take; it's actually the thread that created the `BackgroundWorker` that will receive the events, and you must call `RunWorkerAsync` from the same thread. For sanity purposes, that should generally be the UI thread. – Aaronaught May 10 '10 at 22:21
  • 9
    This isn't correct. It will only get raised on the UI thread if the UI thread created the BGW instance. If a thread created the BGW then it will be raised on an arbitrary threadpool thread. – Hans Passant May 11 '10 at 03:26
  • @Hans: What is the difference between what you said and what I said? – Aaronaught May 11 '10 at 12:30
  • 4
    "will be raised on the same thread", not the case if it wasn't created on the UI thread. Marshaling a call to an arbitrary thread is not possible, only the UI thread has the required plumbing. A WindowsFormsSynchronizationContext or DispatcherSynchronizationContext provider is required, the default provider (SynchronizationContext) makes callbacks on a threadpool thread. – Hans Passant May 11 '10 at 13:09
  • 1
    @Hans: Alright, I've made this more clear. As long as we're nitpicking, it doesn't *have* to be the UI thread; you can create your own `SynchronizationContext` that *does* synchronize. – Aaronaught May 11 '10 at 18:36
  • 2
    Also, we got confused because you also have to call RunWorkerAsync from the main thread. That's most important. We had a timer to collect BGWs and kick them off. The difference between System.Timers.Timer and System.Windows.Forms.Timer was the difference between OnWorkerCompleted being invoked from main thread and a random thread. System.Timers.Timer events aren't invoked on the main thread. – Thadeux Jul 25 '12 at 20:10
  • Based on my experience from just now, and based on this post (https://social.msdn.microsoft.com/Forums/vstudio/en-US/e61819bd-dcb6-4d68-a490-1b3ad70147af/backgroundworker-calls-runworkercompleted-event-on-worker-thread?forum=wpf) the RunWorkerCompleted() is called on the thread that called `RunWorkerAsync`. So the first version of your answer was more correct. Also see http://stackoverflow.com/questions/13527442/why-does-the-backgroundworker-not-call-the-runworkercompleted-on-the-right-threa – Tobias Knauss Apr 23 '15 at 11:54
  • 1
    @TobiasKnauss: Nope, the current version is correct. The callback runs within the same *Synchronization Context*, not the same *thread*. If `RunWorkerAsync` is called from a random `ThreadPool` thread, I can practically guarantee you that the callback won't run on the same thread. – Aaronaught Apr 25 '15 at 05:05
0

From my observation, the RunWorkerCompleted is executed on the thread that called RunWorkerAsync, which is not necessarily the thread that CREATED the background worker.

-1

On same issue found this thread msdn.

It's all about the SynchronizationContext.

Windows Forms will automatically overwrite any existing SynchronizationContext by default when it creates a window; see WindowsFormsSynchronizationContext.AutoInstall: http://msdn.microsoft.com/en-us/library/system.windows.forms.windowsformssynchronizationcontext.autoinstall.aspx

BackgroundWoker captures the SynchronizationContext when RunWorkerAsync is called, not when it is constructed (note that this is an implementation detail, not documented). It then uses that captured SynchronizationContext to execute RunWorkerCompleted.

So, the thread that runs RunWokerCompleted is actually determined by SynchronizationContext.Current at the time RunWorkerAsync is called.

WPF provides a DispatcherSynchronizationContext that will marshal the call to its UI thread. Windows Forms provides a WindowsFormsSynchronizationContext that will marshal the call to its UI thread. When doing WPF/Forms interop, both systems share a single UI thread. You mentioned this is all in the context of an MFC app; in this case, MFC doesn't provide a SynchronizationContext (of course), but it would share its thread with the WPF and Forms thread (they all share a single UI thread).

One more piece of information: the default SynchronizationContext will queue actions (e.g., RunWorkerCompleted) to the ThreadPool. This sounds like the behavior you're seeing. This default behavior kicks in if SynchronizationContext.Current is null at the time RunWorkerAsync is called.

So, it sounds like closing out the Windows Forms form may be clearing SynchronizationContext.Current. Windows Forms does have the notion of a "main form", so it may be doing that since it thinks the last form of the application just closed.

I recommend: 1) Testing if SynchronizationContext.Current is in fact null at the time RunWorkerCompleted is called, and possibly also testing before and after the Windows Forms form is displayed. Just to make sure this is the problem. 2) Setting SynchronizationContext.Current (via SynchronizationContext.SetSynchronizationContext) after the Windows Forms form closes. Pass "new DispatcherSynchronizationContext()" as the argument.

Alternatively, if the Windows Forms form is a modal dialog, you can use the ScopedSynchronizationContext class from my Nito.Async library (http://www.codeplex.com/NitoAsync), which is a very simple class used to temporarily replace SynchronizationContext.Current, resetting it to its original value at the end of its "using" block.

More information on the various SynchronizationContext types is on my blog: http://nitoprograms.blogspot.com/2009/10/synchronizationcontext-properties.html

. In short insert

AsyncOperationManager.SynchronizationContext = new DispatcherSynchronizationContext(this.Dispatcher)

before RunWorkerAsync call.

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 28 '21 at 20:34