SynchronizationContext Class

The SynchronizationContext class (System.Threading) provides a mechanism to invoke delegates on the same thread that the SynchronizationContext represents.

/// <summary>
/// Dispatches an asynchronous message to a synchronization context.
/// </summary>
/// <param name="d">The SendOrPostCallback delegate to call</param>
/// <param name="state">The object passed to the delegate</param>
public virtual void Post(SendOrPostCallback d, Object state)

/// <summary>
/// Dispatches a synchronous message to a synchronization context.
/// </summary>
/// <param name="d">The SendOrPostCallback delegate to call</param>
/// <param name="state">The object passed to the delegate</param>
public virtual void Send(SendOrPostCallback d, Object state)

The post and send methods take a SendOrPostCallback delegate that represents the method to be invoked on the same thread that the SynchronizationContext represents, and an object representing information to pass to that delegate.

The SynchronizationContext class provides a static property getter that gets the synchronization context for the current thread.

public static SynchronizationContext Current { get; }

This property is useful for propagating a synchronization context from one thread to another.

How do we use it?

The goal is to ensure that the caller thread into any background worker class creates the Synchronization context, which will then be used to signal the caller on the same thread as the caller when the operation is completed.

public class BackgroundWorker
{
    public event Action<object> WorkCompleted;

    public BackgroundWorker()
    {
        this.context = SynchronizationContext.Current;
        if (this.context == null)
        {
            context = new SynchronizationContext();
        }

        this.workerThread = new Thread(this.DoWork);
        this.workerThread.Start();
    }

    private void DoWork()
    {
        // ...
        // Do some work
        // ...

        // Signal the caller that we are done.
        object results = new object();
        context.Post(delegate(object state)
        {
            if (this.WorkCompleted != null)
            {
                this.WorkCompleted(results);
            }
        }, results);
    }

    private SynchronizationContext context;
    private Thread workerThread;
}

WarningThe above code is dangerous! The creation of a SynchronizationContext instance for a thread can lead to nasal demons. There are possible conditions which may lead to Form’s threads Synchronization context (known as WindowsFormsSynchronizationContext) not being used correctly.

AsyncOperationManager Class to the Rescue

The AsyncOperation and AsyncOperationManager class (System.ComponentModel) removes the requirement for accessing or creating SynchronizationContexts. They provide a thread-safe, context-safe environment to access a handle to the current threads SynchronizationContext, and then a safe means to use this for sending/posting information back to that thread’s context.

The AsyncOperation class wraps the SynchronizationContext, and is used for sending/posting messages.

Use the AsyncOperationManager to retrieve the AsyncOperation class which wraps the SynchronizationContext we’ll use for posting. This safely creates a SynchronizationContext object belonging to the thread in which this operation was called.

Below is the previous example re-written using these new classes.

public class BackgroundWorker
{
    public event Action<object> WorkCompleted;

    public BackgroundWorker()
    {
        this.asyncOperation = AsyncOperationManager.CreateOperation(null);

        this.workerThread = new Thread(this.DoWork);
        this.workerThread.Start();
    }

    private void DoWork()
    {
        object results = new object();
        this.asyncOperation.Post(delegate(object state)
        {
            if (this.WorkCompleted != null)
            {
                this.WorkCompleted(results);
            }
        }, results);
    }

    private AsyncOperation asyncOperation;
    private Thread workerThread;
}