BackgroundWorker and SynchronizationContext
I have mentioned umpteen number of times earlier - .NET 2.0 comes with a wealth of new classes that have been added to the BCL. Today’s turn is of BackgroundWorker. If you look at the available documentation (circa Beta 2), this is what you will find:
“Executes an operation on a separate thread”. You first reaction would be “Heck, what’s so “new” about that?” Okay, so let’s spelunk into its properties and methods and try to find out.
|
Methods |
|
RunWorkerAsync |
Starts the designated method on a separate threadpool thread. |
|
CancelAsync |
Requests the cancellation of the pending background operation |
|
Events |
|
ProgressedChanged |
This is called when the ReportProgress method is called |
|
RunWorkerCompleted |
Occurs when the background operation is completed |
|
DoWork |
Occurs when the RunAsync method is called. |
Again, after looking at these, you thoughts wouldn’t change – “Still, what’s so great about the same? I had created similar helpers in .NET 1.1.”. Fine, let me tell what’s new. The very interesting aspect about the BackgroundWorker is the fact that RunWorkerCompleted and ProgressChanged event handlers would execute on the thread that created the BackgroundWorker object – most typically on a Windows form main thread (the thread on which the UI controls are created). Therefore, you now do not fall into a trap that you used to fall into earlier – updating a winform control on the wrong thread and facing unpredictable results. (Note that the DoWork method runs on a separate threadpool thread – do not update UI controls on that method)
So, you would now say: “Cool, the BackgroundWorker probably has a reference to the ISynchronizeInvoke object, and it would call Invoke on it to dispatch the method on the right thread. (System.Windows.Forms.Contrtol class implements the ISynchronizeInvoke interface) ”.
If you look closely, the BackgroundWorker is actually agnostic of the environment it runs in. It could not even be a winform application, or even if it was, it does not really rely on ISynchronizeInvoke directly. So, how does this magic happen?
With the help of the valuable services of Reflector, I came to know about a new class (a set of them actually) introduced in .NET 2.0, which
are responsible for this magic – SynchronizationContext. Here’s what the documentation says:
“Provides the basic functionality for propagating a synchronization context in various synchronization models. The purpose of the synchronization model implemented by this class is to allow the internal asynchronous/synchronization operations of the common language runtime (CLR) to behave properly with different synchronization models. This model also simplifies some of the requirements that managed applications have had to follow in order to work correctly under different synchronization environments”
Can’t gather much from this explanation isn’t it? What is does mean perhaps is no more Control.Invoke. But it appears as if it is analogous to the thread’s Context object we had earlier, and a bit more specialized. By now, it would have rung another bell with you. As of .NET 2.0, in the case of Thread.Start method, the security context of the creating thread would be passed to the newly created thread. Guess what is behind this?
I tried a hand at this new class, simple stuff actually. I created a thread from the button click handler on a winform app and updated a control from the thread method (through anymymous method in this case). This is the snippet I tried:
private void button1_Click(object sender, EventArgs e)
{
ctx = SynchronizationContext.Current;
// thanks to anonymous methods, everything here is inline
// thanks to me, this is confusing
ThreadPool.QueueUserWorkItem(
delegate(object state)
{
// would be true
Debug.WriteLine(Thread.CurrentThread.IsThreadPoolThread.ToString());
ctx.Send(delegate(object someState)
{
// would be false
label1.Text = Thread.CurrentThread.IsThreadPoolThread.ToString();
},null);
}
);
}
Send is the method of the SynchronizationContext class to dispatch the method on the right thread synchronously. To do the same asynchronously, we call Post.
Another interesting thing to note is - in .NET 2.0, if you update a control on the wrong thread, you get an InvalidOperationException. As of 1.1, there was no exception, but the result was unpredicatable.
As you might have already guessed, there is not much documentation yet. But, unlike ContextBoundObject, ContextAttribute and other related classes, you might be seeing a lot of articles and proper documentation in the near future. At least, I shall spelunk a little more and come with an article of sorts.