Click here to Skip to main content
Click here to Skip to main content

The .NET Framework's New SynchronizationContext Class

, 8 May 2007
Rate this:
Please Sign up or sign in to vote.
A new .NET Framework class for making thread synchronization issues easier to manage.

Introduction

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.

Background

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.

The SynchronizationContext Class to the Rescue!

Wouldn't it be nice if Forms 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, Forms 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. Forms 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.

Using the SynchronizationContext Class

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

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.

The BackgroundWorker Class

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.

Conclusion

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.

History

  • May 26, 2006 - First version completed.
  • May 7, 2006 - Updated with a warning about using a raw SynchronizationContext object.

License

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

About the Author

Leslie Sanford

United States United States
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.

Comments and Discussions

 
Question[My vote of 2] Caution: This post is faulty PinmemberHRThomann29-May-12 23:21 
AnswerRe: [My vote of 2] Caution: This post is faulty PinmemberMember 87244552-Nov-12 4:31 
GeneralMy vote of 5 Pinmemberlastunicorn4-Aug-10 0:58 
GeneralMy vote of 5 Pinmemberbusireddy21-Jul-10 6:45 
Generalvery nice but difficult to grasp PinmemberMember 639203630-Mar-10 0:46 
Generalvery nice Pinmembermikeperetz24-Dec-08 14:04 
QuestionStoring SynchronizationContext.Current within a Form PinmemberJacob Jose16-Nov-08 23:45 
GeneralAwesome!! PinmemberSyed Mehroz Alam13-Aug-08 19:57 
GeneralThank you! Pinmemberstephenpatten24-Jun-08 10:38 
GeneralCreating my own SynchronizationContext object does not execute on the main thread PinmemberMember 22144714-Apr-08 10:43 
GeneralRe: Creating my own SynchronizationContext object does not execute on the main thread PinmemberLeslie Sanford4-Apr-08 10:48 
GeneralRe: Creating my own SynchronizationContext object does not execute on the main thread PinmemberMember 128921028-May-08 11:51 
GeneralRe: Creating my own SynchronizationContext object does not execute on the main thread Pinmembertzleo27-Jul-09 23:28 
GeneralRe: Creating my own SynchronizationContext object does not execute on the main thread Pinmemberrazvandynalog17-Jan-11 10:40 
GeneralMiscellaneous PinmemberHRThomann2-Apr-08 23:11 
GeneralQuestion Pinmembermikeperetz17-Jan-08 4:56 
GeneralRe: Question PinmemberLeslie Sanford19-Jan-08 20:58 
GeneralNice work Pinmembermikeperetz16-Jan-08 9:33 
GeneralAlternative approach [modified] Pinmembergunters20-Aug-07 0:06 
GeneralExcellent article Pinmemberjuandix2-Aug-07 5:21 
GeneralRe: Excellent article PinmemberLeslie Sanford3-Aug-07 13:01 
GeneralOwn message queue PinmemberName taken1-Aug-07 2:40 
GeneralRe: Own message queue PinmemberLeslie Sanford3-Aug-07 13:02 
GeneralComment about "your guess" PinmemberOne Smart Motor Scooter27-Jun-07 5:11 
GeneralRe: Comment about "your guess" PinmemberLeslie Sanford27-Jun-07 10:33 
GeneralThis code is extremely faulty - BEWARE. PinmemberShadowChaser7-May-07 5:53 
GeneralRe: This code is extremely faulty - BEWARE. PinmemberLeslie Sanford7-May-07 6:11 
GeneralUpdate submitted [modified] PinmemberLeslie Sanford7-May-07 7:35 
GeneralQuestion regarding non UI thread marshalling Pinmemberbilly p19-Oct-06 10:18 
GeneralRe: Question regarding non UI thread marshalling PinmemberLeslie Sanford19-Oct-06 11:17 
GeneralRe: Question regarding non UI thread marshalling Pinmemberbilly p19-Oct-06 12:16 
Leslie,
 
Below is the code. You should be able to just create a Visual Studio 2005 Visual Basic Windows Application, and just replace the Form1 code with my code.
 
I'm listening to your stuff right now - very cool. I like the butterfly pictures too!
 
By the way the Digi 002 rack is by DigiDesign - the guys that make ProTools.
 
______________________________________________________________________________________________
' code starts here
 
Imports System.ComponentModel
 

Public Class Form1
 
Dim MyDataGridView As New DataGridView
Dim MyBillyTypeList As BillyTypeList
Dim MyBindingSource As New BindingSource
 
Public Sub New()
 
' This call is required by the Windows Form Designer.
InitializeComponent()
 
' Add any initialization after the InitializeComponent() call.
 
' Add the DataGridView to the form
Me.Controls.Add(MyDataGridView)
MyDataGridView.Size = New Size(700, 700)
 
' create a new BindList(Of ) object - BillyTypeList inherits BindingList
MyBillyTypeList = New BillyTypeList
' connect the bindinglist to the bindingsource
MyBindingSource.DataSource = MyBillyTypeList
' connecte the bindingsource to the DataGridView
MyDataGridView.DataSource = MyBindingSource
 

' re-arrange the columns a little
MyDataGridView.Columns("p1").DisplayIndex = 0
MyDataGridView.Columns("p2").DisplayIndex = 1
MyDataGridView.Columns("p3").DisplayIndex = 2
 
End Sub
End Class
 

 
Public Class BillyTypeList
Inherits System.ComponentModel.BindingList(Of BillyType)
 
Public Sub New()
Me.AllowNew = True
Me.AllowEdit = True
 
' add some data
Me.Add(New BillyType(1, 2, 3))
Me.Add(New BillyType(4, 5, 6))
 
End Sub
End Class
 

Public Class BillyType
Implements System.ComponentModel.INotifyPropertyChanged
 
Private onPropertyChangeDelegate As System.Threading.SendOrPostCallback
Dim asyncOp As AsyncOperation
 
Dim SeperateThreadBool As Boolean = False
 
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
 
Private Sub NotifyPropertyChanged(ByVal info As String)
If SeperateThreadBool Then
Dim e As New PropertyChangedEventArgs(info)
asyncOp.Post(Me.onPropertyChangeDelegate, e)
Else
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End If
End Sub
 
Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
RaiseEvent PropertyChanged(Me, e)
End Sub
 
Private Sub _PropertyChanged(ByVal state As Object)
Dim e As PropertyChangedEventArgs = CType(state, PropertyChangedEventArgs)
Me.OnPropertyChanged(e)
End Sub
 
Private mytimer As New System.Timers.Timer
 
Private Sub IncrementValues(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
SyncLock Me
Me.SeperateThreadBool = True
p3 = p3 + 1
mystr = "hi " & p3
Me.SeperateThreadBool = False
End SyncLock
End Sub
 
Private _p1 As Double
Public Property p1() As Double
Get
Return _p1
End Get
Set(ByVal value As Double)
_p1 = value
NotifyPropertyChanged("P1")
End Set
End Property
 
Private _p2 As Integer
Public Property p2() As Integer
Get
Return _p2
End Get
Set(ByVal value As Integer)
_p2 = value
NotifyPropertyChanged("P2")
End Set
End Property
 
Private _p3 As BillyThing
Public Property p3() As Double
Get
Return _p3.bp2
End Get
Set(ByVal value As Double)
_p3.bp2 = value
NotifyPropertyChanged("P3")
End Set
End Property
 
Private _mystr As String
Public Property mystr() As String
Get
Return _mystr
End Get
Set(ByVal value As String)
_mystr = value
NotifyPropertyChanged("mystr")
End Set
End Property
 
Public Sub New()
InitializeMe()
mytimer.Start()
 
End Sub
 
Public Sub New(ByVal p1arg As Double, ByVal p2arg As Integer, ByVal p3arg As Double)
InitializeMe()
 
p1 = p1arg
p2 = p2arg
_p3 = New BillyThing
p3 = p3arg
mystr = CType(p3arg + p2arg + p1arg, String)
mytimer.Start()
End Sub
 

 
Private Sub InitializeMe()
p1 = 0
p2 = 0
_p3 = New BillyThing
p3 = 0
 
AddHandler mytimer.Elapsed, AddressOf Me.IncrementValues
mytimer.Interval = 1000
Me.asyncOp = System.ComponentModel.AsyncOperationManager.CreateOperation(Nothing)
Me.onPropertyChangeDelegate = New System.Threading.SendOrPostCallback(AddressOf _PropertyChanged)
 
End Sub
 

 
End Class
 
Public Class BillyThing
 
Private _bp1 As Integer
Default Public Property bp1(ByVal index As Integer) As Integer
Get
Return _bp1
End Get
Set(ByVal value As Integer)
_bp1 = value
End Set
End Property
 
Private _bp2 As Double
 
Public Property bp2() As Double
Get
Return _bp2
End Get
Set(ByVal value As Double)
_bp2 = value
End Set
End Property
 

End Class
 


GeneralRe: Question regarding non UI thread marshalling PinmemberLeslie Sanford19-Oct-06 14:43 
GeneralRe: Question regarding non UI thread marshalling Pinmemberbilly p20-Oct-06 0:43 
GeneralRe: Question regarding non UI thread marshalling PinmemberLeslie Sanford20-Oct-06 4:27 
GeneralRe: Question regarding non UI thread marshalling Pinmemberbilly p22-Oct-06 1:55 
GeneralRe: Question regarding non UI thread marshalling Pinmemberjamama7-Mar-07 2:29 
GeneralRe: Question regarding non UI thread marshalling Pinmemberbilly p7-Mar-07 8:05 
GeneralGood article PinmemberJudah Himango26-Jun-06 4:36 
GeneralRe: Good article PinmemberLeslie Sanford27-Jun-06 19:06 
GeneralGreat, but a couple suggestions [modified] PinmemberWilliam Sullivan21-Jun-06 3:47 
GeneralRe: Great, but a couple suggestions PinmemberLeslie Sanford21-Jun-06 6:04 
GeneralRe: Great, but a couple suggestions PinmemberDustin Metzgar21-Jun-06 6:20 
GeneralRe: Great, but a couple suggestions PinmemberLeslie Sanford21-Jun-06 6:24 
QuestionNon-UI threads? PinmemberdzCepheus16-Jun-06 16:31 
AnswerRe: Non-UI threads? PinmemberLeslie Sanford16-Jun-06 16:41 
GeneralRe: Non-UI threads? [modified] PinmemberdzCepheus16-Jun-06 18:18 
GeneralRe: Non-UI threads? [modified] PinmemberLeslie Sanford16-Jun-06 19:13 
GeneralRe: Non-UI threads? PinmemberdzCepheus16-Jun-06 19:31 
GeneralRe: Non-UI threads? PinmemberLeslie Sanford16-Jun-06 19:38 
GeneralRe: Non-UI threads? PinmemberdzCepheus16-Jun-06 19:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 8 May 2007
Article Copyright 2006 by Leslie Sanford
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid