The SynchronizationContext
class is a new class belonging to the .NET Framework's System.Threading
namespace. The purpose of this class is to provide a model to make communication between threads easier and more robust. I will begin by describing how the SynchronizationContext
class helps us handle events using Windows Forms. I will also touch on a few of the other new classes in the System.ComponentModel
which use the SynchronizationContext
class that also help us with synchronization issues.
As a disclaimer, some of what I describe below is based on my experience, albeit brief, with these new classes rather than documentation. The documentation seems a bit sparse at this time. So while my understanding isn't complete, I'm hoping that what I share below will be helpful, and that I will be able to expand this article in the future as my understanding (and hopefully Microsoft's documentation) increases.
Most of us are familiar with the prohibition against modifying a Control
from any thread other than the one in which it was created. Instead, an event generated on another thread must be marshaled to the Control
's thread using its (or the Form
that it belongs to) Invoke
or BeginInvoke
methods. These two methods belong to the ISynchronizeInvoke
interface, which the Control
class implements. Their purpose is to take a delegate and invoke it on the same thread that the ISynchronizeInvoke
object is running. Typically, a Form
's method for responding to an event generated on another thread looks like this:
private void HandleSomeEvent(object sender, EventArgs e)
{
if(InvokeRequired)
{
BeginInvoke(new EventHandler(HandleSomeEvent), sender, e);
}
else
{
// Event logic here.
}
}
This method first checks its InvokeRequired
boolean property, which also belongs to the ISynchronizeInvoke
interface, to see if it needs to marshal the event to its thread. If the property is true
, it calls the BeginInvoke
method, passing it a delegate to the method for handling the event and its arguments. If the property is false
, it executes its logic for responding to the event. In other words, the InvokeRequired
property will be true
if it is checked on a thread other than the one in which the Form
belongs; otherwise, it will be false
.
Note that the delegate being passed to BeginInvoke
represents the same method that handled the event in the first place. If the InvokeRequired
property is true
, this has the effect of invoking the event handler method twice: once when it initially is called in response to the event, and once after it has been marshaled by the Form
with a call to BeginInvoke
. The second time around, the InvokeRequired
property will be false
and the event logic will be run.
Wouldn't it be nice if Form
s didn't have to check to see if an event was raised on another thread? Wouldn't it be nice if a Form
could respond to an event without having to check its InvokeRequired
property? The SynchronizationContext
class solves our problem.
The SynchronizationContext
class represents a conduit through which we can pass delegates to be invoked on the same thread that the SynchronizationContext
represents. It is like the ISynchronizeInvoke
interface in that respect. Corresponding to the ISynchronizeInvoke
's Invoke
and BeginInvoke
methods, the SynchronizationContext
class has Send
and Post
methods. Like the Invoke
method, the SynchronizationContext
's Send
method is for invoking delegates synchronously, and like the BeginInvoke
method, the Post
method is for invoking delegates asynchronously.
Both the Send
and Post
methods take a SendOrPostCallback
delegate representing the method to be invoked. In addition, they also take an object representing state information to pass to the delegate when it is invoked.
At this point, we may be wondering what advantage the SynchronizationContext
class gives us. Why not use the ISynchrnoizeInvoke
interface instead?
Well, this is where things get interesting. The SynchronizationContext
class has a SetSynchronizationContext
static method for setting the current SynchronizationContext
object. What is going on behind the scenes is that the SetSynchronizationContext
static method takes the SynchronizationContext
object passed to it and associates it with the current thread, the thread in which the SetSynchronizationContext
method is being called. Afterwards, the SynchronizationContext
object can be retrieved using the Current
static property. This property represents the SynchronizationContext
object for the current thread, whichever thread you happen to be in when you access the Current
property.
Let's go over this one more time:
When the SetSynchronizationContext
static method is called, it takes the SynchronizationContext
object passed to it, and keeps track of it and the thread it belongs to. It makes sure that when you access the Current
static property, you get back whichever SynchronizationContext
object was set for that thread.
So getting back to our Form
scenario described above, when a Form
is created, a SynchronizationContext
object (actually a class object derived from SynchronizationContext
, called WindowsFormsSynchronizationContext
, more on that later) representing the Form
's thread is set. Other objects can retrieve the Form
's SynchronizationContext
object through the Current
property and use it later for marshaling delegate invocations to the Form
's thread. In this way, Form
s no longer have to check to see if an event they are responding to originated on another thread. The marshaling has already been taken care of elsewhere. In essence, the responsibility for marshaling events has been moved from the receiver of an event to the sender.
This model helps us automate communication between threads. A thread can communicate with another thread safely by using its SynchronizationContext
object.
There's something that I now need to tell you: the SynchronizationContext
class itself doesn't do very much on its own. While it is not an abstract class, it is really meant to be a base class for classes that override its methods to provide meaningful functionality. For example, here is its implementation for the Send
and Post
methods:
public virtual void Send(SendOrPostCallback d, Object state)
{
d(state);
}
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
The Send
method simply invokes the callback delegate, passing it the state argument. The Post
method provides asynchronous behavior by queuing the callback as a work item with the ThreadPool
. So the default behavior of the SynchronizationContext
class doesn't really do a whole lot. The magic comes when we use or create a derived class that does something more meaningful.
An example of a SynchronizationContext
derived class is the WindowsFormsSynchronizationContext
class. Form
s use this class to represent their synchronization context. My guess is that this class is a light wrapper around a Form
's ISynchronizeInvoke
functionality, delegating calls to Send
and Post
to its Invoke
and BeginInvoke
methods, respectively.
How do we write our own classes that use the SynchronizationContext
class? The key thing to remember is that our goal is to get the SynchronizationContext
belonging to the thread in which our class was created so that we can use it later from another thread to send/post events to the original thread. I'm going to give a really simple example. This example does nothing useful, but almost at a glance, it will show us the basic template for using the SynchronizationContext
class:
using System;
using System.Threading;
namespace SynchronizationContextExample
{
public class MySynchronizedClass
{
private Thread workerThread;
private SynchronizationContext context;
public event EventHandler SomethingHappened;
public MySynchronizedClass()
{
// It's important to get the current SynchronizationContext
// object here in the constructor. We want the
// SynchronizationContext object belonging to the thread in
// which this object is being created.
context = SynchronizationContext.Current;
// It's possible that the current thread does not have a
// SynchronizationContext object; a SynchronizationContext
// object has not been set for this thread.
//
// If so, we simplify things by creating a SynchronizationContext
// object ourselves. However! There could be some problems with
// this approach. See the article for more details.
if(context == null)
{
context = new SynchronizationContext();
}
workerThread = new Thread(new ThreadStart(DoWork));
workerThread.Start();
}
private void DoWork()
{
context.Post(new SendOrPostCallback(delegate(object state)
{
EventHandler handler = SomethingHappened;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}), null);
}
}
}
This class gets the SynchronizationContext
for the current thread in its constructor. If this class is being used in a Form
, the current SynchronizationContext
object will allow us to post/send events to the Form
's thread. It's possible, however, that if an instance of this class is being created in, say, a worker thread somewhere, the SynchronizationContext.Current
property may be null
. In other words, the SynchronizationContext
object for the current thread may not have been set. So, it's important to check to see if the Current
property is null
. Here, in the case where it is null
, I just create an instance of the SynchronizationContext
class and rely on its default behavior.
What's nice is that our class doesn't have to know about who it's sending/posting events to. It doesn't have to have an ISynchronizeInvoke
object passed to it to synchronize itself with; it has access to the current SynchronizationContext
object through the SynchronizationContext.Current
property.
Warning! As was pointed out in a post to this article's message board, creating an instance of the SynchronizationContext
class can be dangerous. For example, say that you're writing a Form
based application and an object needs to interact with the main Form
in a thread safe way; the object needs access to the SynchronizationContext
that belongs to the Form
's thread. The object can retrieve this by accessing the SynchronizationContext.Current
property. This will give you the Form
's SynchronizationContext
derived object assuming that the Current
property is checked on the same thread in which the Form
is running. If it's checked from another thread, the Current
property will probably be null. If this is the case, it would be safer to treat this as an error rather than simply create an instance of the SynchronizationContext
class.
The AsyncOperation
and AsyncOperationManager
classes make our life even easier by removing the need for accessing the SynchronizationContext
object directly. Here is the above example rewritten using both of these classes:
using System;
using System.Threading;
using System.ComponentModel;
namespace SynchronizationContextExample
{
public class MySynchronizedClass
{
private Thread workerThread;
private AsyncOperation operation;
public event EventHandler SomethingHappened;
public MySynchronizedClass()
{
operation = AsyncOperationManager.CreateOperation(null);
workerThread = new Thread(new ThreadStart(DoWork));
workerThread.Start();
}
private void DoWork()
{
operation.Post(new SendOrPostCallback(delegate(object state)
{
EventHandler handler = SomethingHappened;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}), null);
operation.OperationCompleted();
}
}
}
It looks as though the AsyncOperationManager
takes care of checking if the current SynchronizationContext
object is null
and providing us with one (wrapped inside an AsyncOperation
object) if that's the case.
An example of a class that uses this new model is the BackgroundWorker
class. This class lets you run an operation in the background as you do other work. It provides functionality for raising events to let you know how the operation is progressing as well as when it has completed. When using a BackgroundWorker
with a Form
, the Form
does not have to check to see if the BackgroundWorker
's events are being raised from a different thread. The BackgroundWorker
class takes care of that for us by using the Form
's SynchronizationContext
object.
There are a few other methods in the SynchronizationContext
class that I haven't covered. To be honest, I'm not sure I understand their purpose. Hopefully, in time, I'll be able to add more details to this article that will provide greater understanding of this new and useful class. In fact, I encourage you to respond with comments and suggestions to make this article more informative. I'm really excited about these new classes as I believe that Microsoft is continuing to make advances in the way that we program.
I hope that you have enjoyed the article, and I look forward to hearing from you. Thanks.
SynchronizationContext
object.This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
Leslie Sanford Member |
Aside from dabbling in BASIC on his old Atari 1040ST years ago, Leslie's programming experience didn't really begin until he discovered the Internet in the late 90s. There he found a treasure trove of information about two of his favorite interests: MIDI and sound synthesis. After spending a good deal of time calculating formulas he found on the Internet for creating new sounds by hand, he decided that an easier way would be to program the computer to do the work for him. This led him to learn C. He discovered that beyond using programming as a tool for synthesizing sound, he loved programming in and of itself. Eventually he taught himself C++ and C#, and along the way he immersed himself in the ideas of object oriented programming. Like many of us, he gotten bitten by the design patterns bug and a copy of GOF is never far from his hands. Now his primary interest is in creating a complete MIDI toolkit using the C# language. He hopes to create something that will become an indispensable tool for those wanting to write MIDI applications for the .NET framework. Besides programming, his other interests are photography and playing his Les Paul guitars.
|