📜 ⬆️ ⬇️

SynchronizationContext - when MSDN fails

I don’t know why, but there’s really little information about this new class in the .NET Framework. MSDN documentation says almost nothing about how to use SynchronizationContext. I must say, initially I myself had a bad idea of ​​the purpose of this class and how to use it. After a long study of the issue, I finally understood his purpose and decided to write this article to help other developers figure it out.

Using SynchronizationContext to code from one thread to another


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

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

Since my code uses SynchronizationContext.Current, let me explain what this static property gives. SynchronizationContext.Current allows you to get the SynchronizationContext that is attached to the current thread. We’ll clarify right away that SynchronizationContext.Current is not a singleton within the AppDomain, but a singleton within the stream. This means that two different threads can get 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 storage (and as I said earlier, not in the global memory of the application domain).
')
Well, let's look at the code that sets the SynchronizationContext in our UI stream:

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:



And what should I 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 it?

For starters, can we really push the code into the UI stream? Yes. If the code is executed in a stream other than a UI stream, you cannot act on the user interface. Do you want to go and try to do it? You will get an exception (in version 1.0 there will be no exception, the application will simply fall, but in version 2.0 there are fatty ugly exceptions that the application will spit out in your face).

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

In terms of design, you do not need references to the UI in the business layer. Then you can leave all UI class synchronization operations, and be sure that the UI is independently responsible for marshaling (see my MVP article). 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 reference to the controls or the form.

And how is this done?

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

In the following example, I have a listBox that is populated from the workflow. The thread simulates the calculations and outputs the data to the listBox. The thread used to update the user interface is started 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()); } } /// <summary> ///      UI  /// </summary> private void UpdateUI(object state) { int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("UpdateUI thread:" + id); string text = state as string; mListBox.Items.Add(text); } } 



Go through the code, note, I display the id of the thread 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. In this flow, I pass the state in which the synchronization context of the UI flow is contained.

 SynchronizationContext uiContext = SynchronizationContext.Current; 


I know that the SynchronizationContext.Current contains the UI thread synchronization context, because the code is executed by pressing a button (UI control). The Run method gets the synchronization context from the passed state, and now it has a way to code pass through the UI stream.

 //      SynchronizationContext uiContext = state as SynchronizationContext; 


The Run method displays a record 1000 times in a listBox. How? It uses the Send context sync method.

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


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

... UpdateUI is a pointer to a method, in the state it contains a string for output in a 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 stream. There is no check for InvokerRequired, because I know that this is a UI stream because the Send method of the UI thread synchronization context was used.

Let's look at the id 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 UI of the thread is 10, the workflow (Run) has an id of three, and when we trigger the update of the user interface, the id of the thread in which it occurs is 10. Everything works as advertised.

Error processing


Very well, we are able to pass the code to the UI stream, but what happens if the code that throws throws an exception? Who is responsible for intercepting it? UI thread or workflow?

Exemption 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); } } } /// <summary> ///    UI  /// </summary> 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.

At start of this code I saw that the exception appeared in a flow of the Run method, but not in UI. This is interesting, because an exception could be expected in the UI stream, taking into account the absence of classes that catch exceptions in the UI stream.
Therefore, there is some magic in the Send method; it executes our code synchronously and returns us any exception that occurred.

Send vs. Post


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

It's time to look in more detail 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); } 



Pay attention to 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 key word here is asynchronous. This means that the Post method will not wait for the delegate to complete, for its own completion. "Shot and forget" about the executable code. It also means that you will not be able to intercept the message, as when calling the Send method. And now the exception will receive a UI stream. If this exception is not processed, the UI thread will drop.

However, Post or Send you choose, the executable code always runs in the right thread. Replacing Send with Post will still get the UI of the stream 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 stream. However, you find that your stream gets null when you call SynchronizationContext.Current. It's okay - you say, and set the SynchronizationContext, if it is not. Primitive. But it 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 you how not to do. This program does not work. Notice, I set the synchronization context in the main thread of the console application. I just create a new instance. And I attach it to the current thread. This is very similar to what a UI thread does when a form is created (not quite, I will explain later). Then I create the Run1 thread, 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 is executed in the same thread as Run1. And not at all in the main thread. Why? What's happening?
Well ... In this part you will understand that there is nothing free in this life. Threads cannot just switch between contexts, they need the infrastructure built into them to perform such an operation. The UI thread, for example, uses a message queue, and in its synchronization context it uses this queue for synchronization in the user interface.

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

/ *

from translator:

Source Code WindowsFormsSynchronizationContext
SynchronizationContext source code

Implementing InvokeRequired in Windows FormsSynchronizationContext
 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 in 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; } } 


* /

Anyway, I found the SynchronizationContext source code, here it is (I deleted the attributes and did some minor formatting):
Basic SynchronizationContext Implementation
 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 switching threads at all). Post does the same thing, simply using a 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 two reasons for contributing to the writing of this article.

Finally



I hope you learned something new for yourself, about the synchronization context 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 basic implementation, as I showed, does nothing to switch threads. The UI thread, in turn, uses the message queue and the Windows API (SendMessage and PostMessage), so I'm sure the code will be executed in the UI thread.

. SynchronizationContext, . . COM, STA . WCF, STA . SynchronizationContext, StaSynchronizationContext, .

From translator

, .. -
 using(var processor = new Processor<int>(handler, exceptionHandler, completedHandler)) { for(int i=0;i<1000000; i++) processor.Push(i); } 

, , . SynchronizationContext UI, . FCL , WPF WinForms.

, , . , , .

Those. , , . UI. , 80% . TPL ( ). .

SynchronizationContext , , UI' -, BeginInvoke.

Source: https://habr.com/ru/post/232169/


All Articles