SynchronizationContext - when MSDN fails

Original author: mikeperetz
  • Transfer
I don’t know why, but there is really not much information about this new class in the .NET Framework. The MSDN documentation says almost nothing about how to use SynchronizationContext. I must say, initially I myself had a poor idea of ​​the purpose of this class and how to use it. After a long study of the issue, I finally understood its purpose and decided to write this article to help other developers figure it out.

Using SynchronizationContext to forward code from one thread to another


Consider some of the technical details of thread communication through the SynchronizationContext. Suppose you have two threads, t1 and t2. And t1 does some work, and at some point wants to pass code execution to t2. One way to do this is to request t2 SynchronizationContext, pass it to t1, which will call the Send method to pass the code to t2. It reminds magic ... However, you should know that not every thread has a SynchronizationContext associated with it. Only one thread has a SynchronizationContext unambiguously; it is a UI thread.

Who sets the SynchronizationContext for the stream UI? Any suggestions? Well, here's the answer for you, the first control created in the stream puts the SynchronizationContext in this stream. This is usually the first form created. How did I know that? Well ... I coded a check.

Since my code uses SynchronizationContext.Current, let me explain what this static property gives. SynchronizationContext.Current allows you to get a SynchronizationContext which is attached to the current thread. Immediately clear, SynchronizationContext.Current is not a singleton within the AppDomain, but a singleton within the stream. This means that two different threads can receive different instances of SynchronizationContext by calling SynchronizationContext.Current. If you are interested in where the current SynchronizationContext is stored, it is stored in the stream data store (and, as I said earlier, not in the global memory of the application domain).

Ok, let's look at the code that sets the SynchronizationContext to our UI thread:

Example
[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    // проверим наличие контекста синхронизации
    var context = SynchronizationContext.Current;
    if (context == null)
        MessageBox.Show("No context for this thread");
    else
        MessageBox.Show("We got a context");
    // создадим форму
    Form1 form = new Form1();
    // проверим наличие контекста синхронизации еще раз
    context = SynchronizationContext.Current;
    if (context == null)
        MessageBox.Show("No context for this thread");
    else
        MessageBox.Show("We got a context");
    if (context == null)
        MessageBox.Show("No context for this thread");
    Application.Run(new Form1());
}



As you can see, there are a couple of points to consider:

  • The first block of code shows that initially there is no SynchronizationContext'a attached to the stream. This is because .NET does not know what will happen in this thread, and there is no executable class that would initialize the synchronization context for this thread.
  • Immediately after creating the form, we see that the context is set. The Form class is responsible for this. It checks if there is no synchronization context, then you should specify it. Remember, the context is always one in one thread, so that any UI control can access it. Because all UI operations must be executed in a UI thread. The stream that creates the window should be able to communicate with this window. In our case, this is the main thread of the application.


And what am I supposed to do with this now?


Now that the UI thread has set the synchronization context, and we can run the code in the UI thread, how can we use this?

For starters, can we really roll code into a UI thread? Yes. If the code is executed in a thread other than the UI of the thread, you cannot act on the user interface. Want to hang around and try to do it? You will get an exception (in version 1.0 there will be no exception, the application will simply crash, but in version 2.0 there are fat ugly exceptions that the application will spit in your face).

In fairness, I will say that you should not use the synchronization context in the UI thread. You need to use the InvokeRequired property (which every class of any UI control has) and see if you need to throw code. If InvokeRequired returns true, then use Control.Invoke to marshal the UI stream. Excellent! But there is a problem with this technique. You should have a control on which you can call Invoke. It does not matter which UI control it will be, but you need at least one available reference to the control, in your non-UI thread, for marshaling.

From a design point of view, you do not need UI links in the business layer. Then you can leave all the synchronization operations to the UI class, and be sure that the UI is solely responsible for marshaling (see my article on MVP). However, this gives the UI more responsibility, and makes the UI more loaded than we would like. It would be nice on the business logic layer to be able to marshal to the user interface, without having links to controls or to the form.

And how is this done?

Yes, primitive, Create a stream, pass the synchronization context to it, and use this stream as a synchronization object for marshaling into a UI stream. Let's see an example.

In the following example, I have a listBox that is populated from a workflow. The stream simulates calculations and outputs data to a listBox. The thread used to update the user interface is launched from the mToolStripButtonThreads_Click handler.

First of all, let's see what is on the form:
See what's on the form
private void InitializeComponent()
    {
        System.ComponentModel.ComponentResourceManager resources =
          new System.ComponentModel.ComponentResourceManager(typeof(Form1));
        this.mListBox = new System.Windows.Forms.ListBox();
        this.toolStrip1 = new System.Windows.Forms.ToolStrip();
        this.mToolStripButtonThreads = new System.Windows.Forms.ToolStripButton();
        this.toolStrip1.SuspendLayout();
        this.SuspendLayout();
        //
        // mListBox
        //
        this.mListBox.Dock = System.Windows.Forms.DockStyle.Fill;
        this.mListBox.FormattingEnabled = true;
        this.mListBox.Location = new System.Drawing.Point(0, 0);
        this.mListBox.Name = "mListBox";
        this.mListBox.Size = new System.Drawing.Size(284, 264);
        this.mListBox.TabIndex = 0;
        //
        // toolStrip1
        //
        this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
        this.mToolStripButtonThreads});
        this.toolStrip1.Location = new System.Drawing.Point(0, 0);
        this.toolStrip1.Name = "toolStrip1";
        this.toolStrip1.Size = new System.Drawing.Size(284, 25);
        this.toolStrip1.TabIndex = 1;
        this.toolStrip1.Text = "toolStrip1";
        //
        // mToolStripButtonThreads
        //
        this.mToolStripButtonThreads.DisplayStyle =
          System.Windows.Forms.ToolStripItemDisplayStyle.Text;
        this.mToolStripButtonThreads.Image = ((System.Drawing.Image)
            (resources.GetObject("mToolStripButtonThreads.Image")));
        this.mToolStripButtonThreads.ImageTransparentColor =
             System.Drawing.Color.Magenta;
        this.mToolStripButtonThreads.Name = "mToolStripButtonThreads";
        this.mToolStripButtonThreads.Size = new System.Drawing.Size(148, 22);
        this.mToolStripButtonThreads.Text = "Press Here to start threads";
        this.mToolStripButtonThreads.Click +=
          new System.EventHandler(this.mToolStripButtonThreads_Click);
        //
        // Form1
        //
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(284, 264);
        this.Controls.Add(this.toolStrip1);
        this.Controls.Add(this.mListBox);
        this.Name = "Form1";
        this.Text = "Form1";
        this.toolStrip1.ResumeLayout(false);
        this.toolStrip1.PerformLayout();
        this.ResumeLayout(false);
        this.PerformLayout();
    }
    #endregion
    private System.Windows.Forms.ListBox mListBox;
    private System.Windows.Forms.ToolStrip toolStrip1;
    private System.Windows.Forms.ToolStripButton mToolStripButtonThreads;
}



And now consider an example:

Example
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void mToolStripButtonThreads_Click(object sender, EventArgs e)
    {
        // посмотрим id потока
        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
        // захватим контекст синхронизации ассоциированный с этим
        // потоком (UI поток), и сохраним его в uiContext
        // отметье что этот контекст устанавливается в UI потоке
        // во время создания формы (вне зоны вашего контроля)
        // также отметье, что не каждый поток имеет контекст синхронизации связанный с ним.
        SynchronizationContext uiContext = SynchronizationContext.Current;
        // Создадим поток и зададим ему метод Run для исполнения
        Thread thread = new Thread(Run);
        // Запустим поток и установим ему контекст синхронизации,
        // таким образом этот поток сможет обновлять UI
        thread.Start(uiContext);
    }
    private void Run(object state)
    {
        // смотри id потока
        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine("Run thread: " + id);
        // вытащим контекст синхронизации из state'а
        SynchronizationContext uiContext = state as SynchronizationContext;
        for (int i = 0; i < 1000; i++)
        {
			// Тут мог бы быть ваш код который обращается к базе
			// или выполняет какие-то вычисления
            Thread.Sleep(10);
            // испольуем UI контекст для обновления интерфейса, 
			// посредством исполнения метода UpdateUI, метод UpdateUI 
			// будет исполнен в UI потоке
            uiContext.Post(UpdateUI, "line " + i.ToString());
        }
    }
    /// 
    /// Этот метод исполняется в основном UI потоке
    /// 
    private void UpdateUI(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        Trace.WriteLine("UpdateUI thread:" + id);
        string text = state as string;
        mListBox.Items.Add(text);
    }
}



Let's go through the code, pay attention, I get the id of the stream so that we can look at it in the future.

When you click on the ToolStrip button, the thread starts with a pointer to the Run method. To this stream, I pass the state in which the synchronization context of the UI of the stream is contained.

SynchronizationContext uiContext = SynchronizationContext.Current;


I know that SynchronizationContext.Current contains the context for synchronizing the UI stream, because the code is executed by clicking on the button (UI control). The Run method gets the synchronization context from the passed state, and now it has a way to forward the code into the UI stream.

// Получение контекста синхронизации из состояния
SynchronizationContext uiContext = state as SynchronizationContext;


The Run method displays an entry in listBox 1000 times. How? It uses the Send method of the synchronization context.

public virtual void Send(SendOrPostCallback d, object state);


The Send method takes two arguments, a delegate to the method and a state. In our example ...
uiContext.Send(UpdateUI, "line " + i.ToString());

... UpdateUI is a pointer to a method, the state contains a string for output to listBox. The code from the UpdateUI method runs in the UI thread, not in the caller.

private void UpdateUI(object state)
{
    int id = Thread.CurrentThread.ManagedThreadId;
    Trace.WriteLine("UpdateUI thread:" + id);
    string text = state as string;
    mListBox.Items.Add(text);
}


Please note that this thread works directly in the UI thread. There is no check on InvokerRequired, because I know that this is a UI stream because the Send method of the synchronization context of the UI stream was used.

Let's look at the id of the threads:
mToolStripButtonThreads_Click thread: 10
Run thread: 3
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
... (x1000 times)

Here we see that the id of the thread UI is 10, the worker thread (Run) has an id of three, and when we call the user interface update, the thread id in which it happens is 10. Everything works as advertised.

Error processing


Very good, we are able to throw code into the UI stream, but what happens if the code is thrown throws an exception? Who is responsible for intercepting it? UI thread or worker thread?

Exception throw example
private void Run(object state)
{
    // смотрим id потока
    int id = Thread.CurrentThread.ManagedThreadId;
    Trace.WriteLine("Run thread: " + id);
    // захватываем контекст синхронизации
    SynchronizationContext uiContext = state as SynchronizationContext;
    for (int i = 0; i < 1000; i++)
    {
        Trace.WriteLine("Loop " + i.ToString());
        // симуляция вычислений
        Thread.Sleep(10);
        // прокидываем код в UI поток
        try
        {
            uiContext.Send(UpdateUI, "line " + i.ToString());
        }
        catch (Exception e)
        {
            Trace.WriteLine(e.Message);
        }
    }
}
/// 
/// Метод исполняемый в UI потоке
/// 
private void UpdateUI(object state)
{
    throw new Exception("Boom");
}



I changed the UpdateUI method to throw an exception. And added try / catch on the Send method of the synchronization context.

When I ran this code, I saw that the exception appeared in the thread of the Run method, and not in the UI. This is interesting because an exception could be expected in the UI thread, given the absence of classes of catching exceptions in the UI thread.
Therefore, there is some magic in the Send method; it executes our code synchronously and returns to us any exception that has occurred.

Send vs. Post


Using the Send method is one of two possible ways to figure out code in a UI thread. The second way is using the Post method. Is there any difference? She is huge!

It's time to take a closer look at the SynchronizationContext class contract.

ISynchronizationContext
// Summary:
//     Provides the basic functionality for propagating a synchronization context
//     in various synchronization models.
public class SynchronizationContext
{
    // Summary:
    //     Creates a new instance of the System.Threading.SynchronizationContext class.
    public SynchronizationContext();
    // Summary:
    //     Gets the synchronization context for the current thread.
    //
    // Returns:
    //     A System.Threading.SynchronizationContext object representing the current
    //     synchronization context.
    public static SynchronizationContext Current { get; }
    // Summary:
    //     When overridden in a derived class, creates a copy of the synchronization
    //     context.
    //
    // Returns:
    //     A new System.Threading.SynchronizationContext object.
    public virtual SynchronizationContext CreateCopy();
    //
    // Summary:
    //     Determines if wait notification is required.
    //
    // Returns:
    //     true if wait notification is required; otherwise, false.
    public bool IsWaitNotificationRequired();
    //
    // Summary:
    //     When overridden in a derived class, responds to the notification that an
    //     operation has completed.
    public virtual void OperationCompleted();
    //
    // Summary:
    //     When overridden in a derived class, responds to the notification that an
    //     operation has started.
    public virtual void OperationStarted();
    //
    // Summary:
    //     When overridden in a derived class, dispatches an asynchronous message to
    //     a synchronization context.
    //
    // Parameters:
    //   d:
    //     The System.Threading.SendOrPostCallback delegate to call.
    //
    //   state:
    //     The object passed to the delegate.
    public virtual void Post(SendOrPostCallback d, object state);
    //
    // Summary:
    //     When overridden in a derived class, dispatches a synchronous message to a
    //     synchronization context.
    //
    // Parameters:
    //   d:
    //     The System.Threading.SendOrPostCallback delegate to call.
    //
    //   state:
    //     The object passed to the delegate.
    public virtual void Send(SendOrPostCallback d, object state);
    //
    // Summary:
    //     Sets the current synchronization context.
    //
    // Parameters:
    //   syncContext:
    //     The System.Threading.SynchronizationContext object to be set.
    public static void SetSynchronizationContext(SynchronizationContext syncContext);
    //
    // Summary:
    //     Sets notification that wait notification is required and prepares the callback
    //     method so it can be called more reliably when a wait occurs.
    protected void SetWaitNotificationRequired();
    //
    // Summary:
    //     Waits for any or all the elements in the specified array to receive a signal.
    //
    // Parameters:
    //   waitHandles:
    //     An array of type System.IntPtr that contains the native operating system
    //     handles.
    //
    //   waitAll:
    //     true to wait for all handles; false to wait for any handle.
    //
    //   millisecondsTimeout:
    //     The number of milliseconds to wait, or System.Threading.Timeout.Infinite
    //     (-1) to wait indefinitely.
    //
    // Returns:
    //     The array index of the object that satisfied the wait.
    [PrePrepareMethod]
    [CLSCompliant(false)]
    public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
    //
    // Summary:
    //     Helper function that waits for any or all the elements in the specified array
    //     to receive a signal.
    //
    // Parameters:
    //   waitHandles:
    //     An array of type System.IntPtr that contains the native operating system
    //     handles.
    //
    //   waitAll:
    //     true to wait for all handles; false to wait for any handle.
    //
    //   millisecondsTimeout:
    //     The number of milliseconds to wait, or System.Threading.Timeout.Infinite
    //     (-1) to wait indefinitely.
    //
    // Returns:
    //     The array index of the object that satisfied the wait.
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    [CLSCompliant(false)]
    protected static int WaitHelper(IntPtr[] waitHandles,
                     bool waitAll, int millisecondsTimeout);
}



Note the comment on the Post method:
//
// Summary:
//	
//     When overridden in a derived class, dispatches an asynchronous message to
//     a synchronization context.
//
// Parameters:
//   d:
//     The System.Threading.SendOrPostCallback delegate to call.
//
//   state:
//     The object passed to the delegate.
public virtual void Post(SendOrPostCallback d, object state);


The keyword here is asynchronous. This means that the Post method will not wait for the delegate to complete, for its own completion. "Shot and forgot" about the executable code. This also means that you will not be able to intercept the message, as when calling the Send method. And now the exception will get the UI thread. If this exception is not handled, the UI thread will fall.

However, you choose Post or Send, the executable code will always work in the right thread. Replacing Send with Post, you still get the stream UI identifier in the executable code.

Now I can use SynchronizationContext to synchronize any threads, right? Nope!



At any time, you can try to use SynchronizationContext from any thread. However, you will find that your thread will get null when you call SynchronizationContext.Current. It's okay, you say, and set the SynchronizationContext if it isn't. Primitively. But that will not work.

Let's look at a program similar to that used previously.

Example
class Program
{
    private static SynchronizationContext mT1 = null;
    static void Main(string[] args)
    {
        // запишем id потока
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("Main thread is " + id);
        // создадим новый контекст синхронизации для текущего потока
        var context = new SynchronizationContext();
        // зададим контекст синхронизации текущему потоку
        SynchronizationContext.SetSynchronizationContext(context);
        // создадим новый поток и передадим ему контекст синхронизации
        Thread t1 = new Thread(new ParameterizedThreadStart(Run1));
        t1.Start(SynchronizationContext.Current);
        Console.ReadLine();
    }
    static private void Run1(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("Run1 Thread ID: " + id);
        // вытаскиваем контекст синхронизации из состояния
        var context = state as SynchronizationContext;
        // пробуем выполнить код в основном потоке используя контекст синхронизации
        context.Send(DoWork, null);
        while (true)
            Thread.Sleep(10000000);
    }
    static void DoWork(object state)
    {
        int id = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine("DoWork Thread ID:" + id);
    }
}



This simple console application shows how you should not do. This program does not work. Notice that I set the synchronization context in the main thread of the console application. I just create a new instance. And attach it to the current thread. This is very similar to what the UI thread does when the form is created (not really, I will explain later). Then I create the thread Run1, and send it the synchronization context of the main thread. When I try to call the Send method, looking at the output, I see that the method is being called in the Run1 thread, and not in the main thread, as expected. Here is the conclusion:
Main thread is 10
Run1 Thread ID: 11
DoWork Thread ID:11


You see, DoWork runs in the same thread as Run1. And not at all in the main stream. Why? What's happening?
Well ... In this part you will understand that there is nothing free in this life. Streams cannot just switch between contexts, they need the infrastructure built into them to carry out such an operation. A UI thread, for example, uses a message queue, and in its synchronization context, it uses this queue to synchronize in the user interface.

Those. The UI thread has its own synchronization context, but this class is derived from SynchronizationContext, and it is called System.Windows.Forms.WindowsFormsSynchronizationContext. And this class has very significant differences from the base implementation of SynchronizationContext. The UI version redefines the calls to the Send and Post methods, and implements the concept of a message queue (I tried to find the source code of this class but did not find it). What does the base implementation of SynchronizationContext do?

/ *

from the translator:

WindowsFormsSynchronizationContext
source code SynchronizationContext source code

Implementing InvokeRequired on WindowsFormsSynchronizationContext
public bool InvokeRequired 
{
    get 
	{
        using (new MultithreadSafeCallScope())
        {
            HandleRef hwnd;
            if (IsHandleCreated) {
                hwnd = new HandleRef(this, Handle);
            }
            else {
                Control marshalingControl = FindMarshalingControl();
                if (!marshalingControl.IsHandleCreated) {
                    return false;
                }
                hwnd = new HandleRef(marshalingControl, marshalingControl.Handle);
            }
            int pid;
            int hwndThread = SafeNativeMethods.GetWindowThreadProcessId(hwnd, out pid);
            int currentThread = SafeNativeMethods.GetCurrentThreadId();
            return(hwndThread != currentThread);
        }
    }
}


Implementing Invoke on WindowsFormsSynchronizationContext
private Object MarshaledInvoke(Control caller, Delegate method, Object[] args, bool synchronous) 
{
  // Marshaling an invoke occurs in three steps:
  //
  // 1.  Create a ThreadMethodEntry that contains the packet of information
  //     about this invoke.  This TME is placed on a linked list of entries because
  //     we have a gap between the time we PostMessage and the time it actually
  //     gets processed, and this gap may allow other invokes to come in.  Access
  //     to this linked list is always synchronized.
  //
  // 2.  Post ourselves a message.  Our caller has already determined the
  //     best control to call us on, and we should almost always have a handle.
  //
  // 3.  If we're synchronous, wait for the message to get processed.  We don't do
  //     a SendMessage here so we're compatible with OLE, which will abort many
  //     types of calls if we're within a SendMessage.
  //
  if (!IsHandleCreated) {
      throw new InvalidOperationException(SR.GetString(SR.ErrorNoMarshalingThread));
  }
  // We have to demand unmanaged code permission here for the control hosted in
  // the browser case. Without this check, we will expose a security hole, because
  // ActiveXImpl.OnMessage() will assert unmanaged code for everyone as part of
  // its implementation.
  // The right fix is to remove the Assert() on top of the ActiveXImpl class, and
  // visit each method to see if it needs unmanaged code permission, and if so, add
  // the permission just to that method(s).
  //
  ActiveXImpl activeXImpl = (ActiveXImpl)Properties.GetObject(PropActiveXImpl);
  if (activeXImpl != null) {
      IntSecurity.UnmanagedCode.Demand();
  }
  // We don't want to wait if we're on the same thread, or else we'll deadlock.
  // It is important that syncSameThread always be false for asynchronous calls.
  //
  bool syncSameThread = false;
  int pid; // ignored
  if (SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, Handle), out pid) == SafeNativeMethods.GetCurrentThreadId()) {
      if (synchronous)
          syncSameThread = true;
  }
  // Store the compressed stack information from the thread that is calling the Invoke()
  // so we can assign the same security context to the thread that will actually execute
  // the delegate being passed.
  //
  ExecutionContext executionContext = null;
  if (!syncSameThread) {
      executionContext = ExecutionContext.Capture();
  }
  ThreadMethodEntry tme = new ThreadMethodEntry(caller, this, method, args, synchronous, executionContext);
  lock (this) {
      if (threadCallbackList == null) {
          threadCallbackList = new Queue();
      }
  }
  lock (threadCallbackList) {
      if (threadCallbackMessage == 0) {
          threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
      }
      threadCallbackList.Enqueue(tme);
  }
  if (syncSameThread) {
      InvokeMarshaledCallbacks();
  }  else {
      // 
      UnsafeNativeMethods.PostMessage(new HandleRef(this, Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
  }
  if (synchronous) {
      if (!tme.IsCompleted) {
          WaitForWaitHandle(tme.AsyncWaitHandle);
      }
      if (tme.exception != null) {
          throw tme.exception;
      }
      return tme.retVal;
  }
  else {
      return(IAsyncResult)tme;
  }
}


* /

One way or another, I found the source code of SynchronizationContext, here it is (I removed the attributes and did a minor formatting):
Basic implementation of SynchronizationContext
namespace System.Threading
{
    using Microsoft.Win32.SafeHandles;
    using System.Security.Permissions;
    using System.Runtime.InteropServices;
    using System.Runtime.CompilerServices;
    using System.Runtime.ConstrainedExecution;
    using System.Reflection;
    internal struct SynchronizationContextSwitcher : IDisposable
    {
        internal SynchronizationContext savedSC;
        internal SynchronizationContext currSC;
        internal ExecutionContext _ec;
        public override bool Equals(Object obj)
        {
            if (obj == null || !(obj is SynchronizationContextSwitcher))
                return false;
            SynchronizationContextSwitcher sw = (SynchronizationContextSwitcher)obj;
            return (this.savedSC == sw.savedSC &&
                    this.currSC == sw.currSC && this._ec == sw._ec);
        }
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }
        public static bool operator ==(SynchronizationContextSwitcher c1,
                                       SynchronizationContextSwitcher c2)
        {
            return c1.Equals(c2);
        }
        public static bool operator !=(SynchronizationContextSwitcher c1,
                                       SynchronizationContextSwitcher c2)
        {
            return !c1.Equals(c2);
        }
        void IDisposable.Dispose()
        {
            Undo();
        }
        internal bool UndoNoThrow()
        {
            if (_ec  == null)
            {
                return true;
            }
            try
            {
                Undo();
            }
            catch
            {
                return false;
            }
            return true;
        }
        public void Undo()
        {
            if (_ec  == null)
            {
                return;
            }
            ExecutionContext  executionContext =
              Thread.CurrentThread.GetExecutionContextNoCreate();
            if (_ec != executionContext)
            {
                throw new InvalidOperationException(Environment.GetResourceString(
                          "InvalidOperation_SwitcherCtxMismatch"));
            }
            if (currSC != _ec.SynchronizationContext)
            {
                throw new InvalidOperationException(Environment.GetResourceString(
                          "InvalidOperation_SwitcherCtxMismatch"));
            }
            BCLDebug.Assert(executionContext != null, " ExecutionContext can't be null");
            // restore the Saved Sync context as current
            executionContext.SynchronizationContext = savedSC;
            // can't reuse this anymore
            _ec = null;
        }
    }
    public delegate void SendOrPostCallback(Object state);
    [Flags]
    enum SynchronizationContextProperties
    {
        None = 0,
        RequireWaitNotification = 0x1
    };
    public class SynchronizationContext
    {
        SynchronizationContextProperties _props = SynchronizationContextProperties.None;
        public SynchronizationContext()
        {
        }
        // protected so that only the derived sync
        // context class can enable these flags
        protected void SetWaitNotificationRequired()
        {
            // Prepare the method so that it can be called
            // in a reliable fashion when a wait is needed.
            // This will obviously only make the Wait reliable
            // if the Wait method is itself reliable. The only thing
            // preparing the method here does is to ensure there
            // is no failure point before the method execution begins.
            RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait));
            _props |= SynchronizationContextProperties.RequireWaitNotification;
        }
        public bool IsWaitNotificationRequired()
        {
            return ((_props &
              SynchronizationContextProperties.RequireWaitNotification) != 0);
        }
        public virtual void Send(SendOrPostCallback d, Object state)
        {
            d(state);
        }
        public virtual void Post(SendOrPostCallback d, Object state)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
        }
        public virtual void OperationStarted()
        {
        }
        public virtual void OperationCompleted()
        {
        }
        // Method called when the CLR does a wait operation
        public virtual int Wait(IntPtr[] waitHandles,
                       bool waitAll, int millisecondsTimeout)
        {
            return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
        }
        // Static helper to which the above method
        // can delegate to in order to get the default
        // COM behavior.
        protected static extern int WaitHelper(IntPtr[] waitHandles,
                         bool waitAll, int millisecondsTimeout);
        // set SynchronizationContext on the current thread
        public static void SetSynchronizationContext(SynchronizationContext syncContext)
        {
            SetSynchronizationContext(syncContext,
              Thread.CurrentThread.ExecutionContext.SynchronizationContext);
        }
        internal static SynchronizationContextSwitcher
          SetSynchronizationContext(SynchronizationContext syncContext,
          SynchronizationContext prevSyncContext)
        {
            // get current execution context
            ExecutionContext ec = Thread.CurrentThread.ExecutionContext;
            // create a switcher
            SynchronizationContextSwitcher scsw = new SynchronizationContextSwitcher();
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // attach the switcher to the exec context
                scsw._ec = ec;
                // save the current sync context using the passed in value
                scsw.savedSC = prevSyncContext;
                // save the new sync context also
                scsw.currSC = syncContext;
                // update the current sync context to the new context
                ec.SynchronizationContext = syncContext;
            }
            catch
            {
                // Any exception means we just restore the old SyncCtx
                scsw.UndoNoThrow(); //No exception will be thrown in this Undo()
                throw;
            }
            // return switcher
            return scsw;
        }
        // Get the current SynchronizationContext on the current thread
        public static SynchronizationContext Current
        {
            get
            {
                ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
                if (ec != null)
                    return ec.SynchronizationContext;
                return null;
            }
        }
        // helper to Clone this SynchronizationContext,
        public virtual SynchronizationContext CreateCopy()
        {
            // the CLR dummy has an empty clone function - no member data
            return new SynchronizationContext();
        }
        private static int InvokeWaitMethodHelper(SynchronizationContext syncContext,
            IntPtr[] waitHandles,
            bool waitAll,
            int millisecondsTimeout)
        {
            return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
        }
    }
}



Look at the implementation of 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);
}


Send simply executes the delegate in the calling thread (without doing any thread switching at all). Post does the same, just using the thread pool for asynchrony. In my opinion this class should be abstract. Such an implementation is only confusing and, moreover, useless. This is one of the two reasons that contributed to this article.

Finally



I hope you learned something new for yourself about the context of synchronization and how to use it. In .NET, I found two classes that implement the synchronization context for the user interface, one for WinForms and one for WPF. I am sure that there are more of them, but so far I have found only them. The base implementation, as I have shown, does nothing to switch flows. The UI thread, in turn, uses the message queue and the Windows API (SendMessage and PostMessage), so I'm sure that the code will be executed in the UI thread.

However, this is not the limit of using this class. You can make your own implementation of SynchronizationContext, it’s not so difficult. In fact, I had to write one such. In my work, it was required that all COM calls be made in the STA thread. Nevertheless, our application uses a pool of threads and WCF, and it was not easy to forward the code into the STA stream. Therefore, I decided to make my version of SynchronizationContext, called StaSynchronizationContext, which will be discussed in the second part of the article.

From translator

The synchronization context interested me when I tried to solve the problem of writing a multi-threaded handler of similar tasks, i.e. sort of
using(var processor = new Processor(handler, exceptionHandler, completedHandler))
{
    for(int i=0;i<1000000; i++)
        processor.Push(i);
}

, and the first impression of the articles found on the synchronization context is what you need. But after a more detailed study, it became clear that the SynchronizationContext appeared and developed as part of the task of interacting with the UI, and its use is limited to these tasks. Actually in FCL itself, only two classes are inherited from it, one for WPF and one for WinForms.

In simple terms, this whole model can be represented as a singleton, the state of which is controlled by a set of consecutive commands from one specific thread, but the commands themselves can be submitted from different threads. The synchronization context itself in this case can be represented as a thread-safe queue of these commands, into which everyone writes, and from which one reads.

Those. the solution is kind of good, but as stated in the article, the basic implementation does not at all implement thread switching. And the existing advanced implementations are narrowly focused on the UI. Writing your implementation will be overloaded with basic methods, 80% of which are most likely not useful. As a result, it turns out that for your tasks it will be easier to use TPL or your own implementation of the context for threads (which was done by the result). Well, or as the author of the article in specific tasks.

Nevertheless, in some cases, understanding the operation of the SynchronizationContext can be very useful, for example, as shown above, when managing a UI from a business layer, without polluting the code on the form with calls through BeginInvoke.

Also popular now: