Multithreading: Introducing synchronization contexts
In the last post, we’ve seen how to marshal back the results obtained on a secondary thread so that controls are updated on the GUI thread. Today we’re going to start looking at synchronization contexts. Synchronization contexts are abstractions for marshalling between threads. In other words, they abstract those scenarios where you cannot call a method from the current thread and need to make sure that the method is executed on a specific thread (as we’ve seen, GUIs fall in this kind of scenario).
As we’ve seen in the previous example, GUI controls can only be updated from the GUI thread and in that example, we’ve relied on the ISynchronizeInvoke method for running the code on the correct thread. In practice, whenever we’ve got a reference to a control, we can use its Invoke or BeginInvoke method to marshal work back to the current thread. The problem is getting a reference to the control. When we build the form, this is simple because we know the controls that are placed on the form and it is easy to get a reference to any of them.
However, suppose we’re building a general component that needs to be reused on several GUI apps. Now things become harder and we’d need to write boilerplate code for getting a control so that we can marshal work back into the GUI thread. This is where synchronization contexts step in and save the day. Before delving into the specifics of the synchronization contexts used on GUIs, lets take a step back an study the general API introduced by the base SynchronizationContext class:
public class SynchronizationContext {
public virtual SynchronizationContext CreateCopy();
public bool IsWaitNotificationRequired();
public virtual void OperationCompleted();
public virtual void OperationStarted();
public virtual void Post(SendOrPostCallback d,
object state);
public virtual void Send(SendOrPostCallback d,
object state);
public virtual int Wait(IntPtr[] waitHandles,
bool waitAll,
int millisecondsTimeout);
public static SynchronizationContext Current { get; }
public static void SetSynchronizationContext(
SynchronizationContext syncContext);
}
As you can see, you can get (Current property) or set (by calling the static SetSynchronizationContext) a thread’s synchronization context. Synchronization contexts are always obtained through the “current” ExecutionContext (which you can get through the ExecutionContext property of the thread class). Notice that if you want to interact with synchronization contests, you must be prepared for getting a null reference.
Even though a synchronization context is part of the ExecutionContext, it’s important to keep in mind that queuing an item on the thread pool will never propagate the “current” SynchronizationContext (not even when you use the QueueUserWorkItem method – event though the execution context is propagated, the synchronization context is one of the items that will not be propagated to the new thread).
Post and Send are probably the most important methods exposed by the class. As you can see, both expect a SendOrPostDelegate and an option object parameter for passing some state to that delegate. The difference is that Post performs the operation in asynchronous fashion while Send performs a synchronous callback. The default implementation of these methods is rather simple: Post uses the thread pool while send simply invokes the delegate.
The OperationStarted and OperationCompleted methods let you receive notifications when an operation starts and ends. The default SynchronizationContext doesn’t do anything in these methods, but as we’ll see, they can be used for (for example) tracking running operations.
Finally, we’ve got a Wait method. The CLR uses this method for waiting if you the IsWaitNotificationRequired property is set to true. What this means is that whenever you wait on a thread that has a synchronization context that has this property set to true, your wait calls end up being redirected to this Wait method. Custom synchronization contexts override this method to perform some specific actions whenever a thread needs to wait on a specific action.
And that’s it. After this brief introduction, we’re ready to take a look at the synchronization contexts used by windows forms. But we’ll leave that to the next post. Keep tuned!