搞懂 SynchronizationContext

SynchronizationContext -MSDN 很让人失望

我不知道为何,目前在.Net下关于这个类只有不多的资料。MSDN文档也只有不多的关于如何使用SynchronizationContext的资料。最开始的时候,我不得不说我在理解为何有这个类以及怎么使用这个类上经历了一段困难的时间。经过阅读大量的相关资料,我最终搞明白了这个类的目的以及它应该如何去使用。我决定写这篇文章来帮助其余开发者理解如何使用这个类,这个类能干吗以及它不能干吗。php

使用SynchronizationContext来封装一段来自一个线程的代码到另外一个线程执行

让咱们先来了解一些不常见的技术点以帮助咱们展现如何使用这个类。SynchronizationContext可使一个线程与另外一个线程进行通讯。假设你又两个线程,Thead1和Thread2。Thread1作某些事情,而后它想在Thread2里面执行一些代码。一个能够实现的方式是请求Thread获得SynchronizationContext这个对象,把它给Thread1,而后Thread1能够调用SynchronizationContext的send方法在Thread2里面执行代码。听起来很绕口...可是这就是你须要了解的东西。不是每个线程都有一个SynchronizationContext对象。一个老是有SynchronizationContext对象的是UI线程。css

谁把SynchronizationContext对象放到UI线程里的?有没有能够猜一下的?放弃思考了?好吧,我来告诉你答案吧。答案是在这个线程里的一个控件(control)被建立的时候会把SynchronizationContext对象放到这个线程里。通常来讲,第一次会是form第一次建立的时候。我怎么知道的?好吧,我一会就给你证实。windows

由于个人代码用了SynchronizationContext.Current,因此就让我先来解释下这个静态属性到底能干吗吧。SynchronizationContext.Current可使咱们获得一个当前线程的SynchronizationContext的对象。咱们必须清楚以下问题:SynchronizationContext.Current对象不是一个AppDomain一个实例的,而是每一个线程一个实例。这就意味着两个线程在调用Synchronization.Current时将会拥有他们本身的SynchronizationContext对象实例。若是你好奇这个context上下文对象怎么存储的,那么答案就是它存储在线程data store(就像我以前说的,不是在appDomain的全局内存空间)。api

来吧,让咱们看下在咱们UI线程中使用的SynchronizationContext的代码吧。app

[STAThread]
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // let's check the context here var context = SynchronizationContext.Current; if (context == null) MessageBox.Show("No context for this thread"); else MessageBox.Show("We got a context"); // create a form Form1 form = new Form1(); // let's check it again after creating a form 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()); }

正如你所见,有以下几个点须要注意:异步

  1. 第一个messagebox将会显示这个线程没有context。由于.Net都不知道这个线程就会作什么,所以没有一个运行时类来在为这个线程初始化sync Context对象。
  2. 在form建立以后,咱们就会发现context已经被设置了。Form类负责了这件事情。它会检测sync Context是否已经有了,若是没有,它就会给线程设置一个。记住context对象在一个线程里面是同样的,因此任何UI控件均可以访问它,由于全部的UI操做都必须在UI线程里面执行。再通俗点说,建立window的线程均可以与window通讯。在咱们的场景下,这个线程就是应用程序的主线程。

怎么使用它?

既然UI线程已经足够nice了,它给了咱们一个Sync Context来使咱们在UI线程下执行代码,那么咱们如何写呢?async

第一步,咱们要肯定咱们真有须要给UI线程封送的代码么?答案确定是“是”。若是你在一个不是UI线程的线程里面执行代码,但你不得不去更新UI。要不要成为一个尝试下的英雄?惋惜的是,你会获得一个异常(在.net1.0都没有引起异常,它只会让你的程序挂掉,在.net2.0中,就会给你引起一个很恶心的异常)。ide

公平的说,我得说你不是必须得用这个类来与UI线程进行通讯。你可使用InvokeRequired属性(在每一个UI control里面都有)来封送你的代码。若是你经过InvokeRequired获得一个“true”,你就可使用Control.Invoke方法来封送代码到UI线程。很是好!那为何还要继续读个人文章呢?可是,这个技术一个问题,你必须得有一个Control你才能调用invoke方法。在UI线程里面这没有什么,可是若是在非UI的线程里面,你若是还想封送代码,你就只能在你的非UI线程里面增长一个control了。从设计的角度来讲,在业务逻辑层应该永远都没有一个UI的引用。因此,你将全部的同步代码都放到了UI类里面来让IU保证封送。可是,这将会增长UI的功能复杂度,使UI完成比咱们但愿的多得多的功能。我必须说,让一个没有任何Control或者Form引用的业务逻辑层来负责封送代码到UI层是一个更好的选择。oop

那到底怎么作呢?ui

简单来讲,建立一个线程,给他sync context对象,而后就能够用这个对象给UI线程封送代码了。让咱们看个例子吧。

在接下来的例子中,有一个list box在工做线程调用。这个线程完成了一些计算而后写到UI的listbox里面。这个线程经过mToolStripButtonThreads_Click事件响应来更新UI。

首先,让咱们先看下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; }

如今让咱们看这个例子:

public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void mToolStripButtonThreads_Click(object sender, EventArgs e) { // let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id); // grab the sync context associated to this // thread (the UI thread), and save it in uiContext // note that this context is set by the UI thread // during Form creation (outside of your control) // also note, that not every thread has a sync context attached to it. SynchronizationContext uiContext = SynchronizationContext.Current; // create a thread and associate it to the run method Thread thread = new Thread(Run); // start the thread, and pass it the UI context, // so this thread will be able to update the UI // from within the thread thread.Start(uiContext); } private void Run(object state) { // lets see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("Run thread: " + id); // grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext; for (int i = 0; i < 1000; i++) { // normally you would do some code here // to grab items from the database. or some long // computation Thread.Sleep(10); // use the ui context to execute the UpdateUI method, // this insure that the UpdateUI method will run on the UI thread. uiContext.Post(UpdateUI, "line " + i.ToString()); } } /// <summary> /// This method is executed on the main UI thread. /// </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); } }

先浏览一遍这个代码。你应该注意到我将每一个方法的线程ID都打印出来来方便咱们一会回顾。

好比:

// let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);

当点击toolstrip button的时候,一个线程将会执行Run方法,须要注意的是我给这个线程传了一个state进去。我在调用的时候传入了UI线程的sync context对象。

SynchronizationContext uiContext = SynchronizationContext.Current;

由于我在toolstrip button的事件响应中执行,因此我知道我在UI线程中。经过调用SynchronizationContext.Current,我能够从UI线程获得sync context对象。

Run 将从它的state里面获得SynchronizationContext对象,这样它就有了像UI线程封送的代码的能力。

// grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext;

Run方法将会在listbox里面写1000行。怎么办呢?须要先用SynchronizationContext中的send方法:

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

SynchronizationContext.Send方法有两个参数,一个是指向一个方法的委托,一个是"state"对象。在咱们的例子里是这样的:

uiContext.Send(UpdateUI, "line " + i.ToString());

UpdateUI就是咱们给这个委托传的方法,“state”是咱们想给listbox增长的string。在UpdateUI中的代码是在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); }

注意到这个代码直接在UI线程中执行。这里没有检查InvokerRequired,由于我知道因为使用了UI线程中的SynchronizationContext的send方法,它就会运行在UI线程中。

让咱们来看下线程的id:

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)

上面能够看到UI线程是10,工做线程(Run)是3,当咱们更新UI的时候,咱们又回到了线程10(UI线程)。这样全部的事情就像咱们想的同样。

错误处理

很好,咱们已经有能力像UI线程封送代码了,可是当咱们封送的代码有引起异常的时候会怎么样?谁来捕获它呢?UI线程仍是工做线程?

private void Run(object state) { // let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("Run thread: " + id); // grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext; for (int i = 0; i < 1000; i++) { Trace.WriteLine("Loop " + i.ToString()); // normally you would do some code here // to grab items from the database. or some long // computation Thread.Sleep(10); // use the ui context to execute the UpdateUI method, this insure that the // UpdateUI method will run on the UI thread. try { uiContext.Send(UpdateUI, "line " + i.ToString()); } catch (Exception e) { Trace.WriteLine(e.Message); } } } /// <summary> /// This method is executed on the main UI thread. /// </summary> private void UpdateUI(object state) { throw new Exception("Boom"); }

我修改了UpdateUI方法来抛出一个异常:

throw new Exception("Boom");

固然,我也同时修改了Run方法在Send方法增长了try/catch。

try { uiContext.Send(UpdateUI, "line " + i.ToString()); } catch (Exception e) { Trace.WriteLine(e.Message); }

当执行这个代码的时候,我发现异常在Run方法中被捕获而不是在UI线程中。这颇有趣,由于咱们本觉得会是UI线程因为没有异常处理而挂掉。

综上,Send方法施展了一个小魔法:它让咱们的代码在别的线程执行,可是在当前线程引起异常。

Send 仍是 Post

Send只是咱们能够向UI线程封送代码的一种方式。另外一种是Post。二者之间有什么不一样呢?不少!可能如今须要更多的了解这个类的细节了,咱们先来看下SynchronizationContext的接口:

// 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); }

下面是Post方法的注释:

// // 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);

这里的关键字是asynchronous,这意味着Post将不会等委托方法的执行完成。Post将会对委托的代码执行而后忘记。这同时也意味着你不能像咱们以前用Send方法同样捕获异常。假设有一个异常被抛出了,它将在UI线程捕获;若是不处理就会停掉UI线程。

无论怎么说,Post也好,Send也好,都将在当前的线程里执行委托。用Post替换掉Send,你就能够获得在UI线程里执行的正确的线程id。

这样的话,我就能够用SynchronizationContext来进行任意我想的线程同步了么?不能!

如今,你可能会在任何线程中用SynchronizationContext。可是,你很快就会发现不是在每次用SynchronizationContext.Current的时候都会有SynchronizationContext实例,它常常会返回null。不用你说,你能够很简单地在没有Sync Context的时候建立一个。这确实很简单,但也确实没用。

让咱们看一个与刚才的UI线程例子很相似的代码:

class Program { private static SynchronizationContext mT1 = null; static void Main(string[] args) { // log the thread id int id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Main thread is " + id); // create a sync context for this thread var context = new SynchronizationContext(); // set this context for this thread. SynchronizationContext.SetSynchronizationContext(context); // create a thread, and pass it the main sync 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); // grab the sync context that main has set var context = state as SynchronizationContext; // call the sync context of main, expecting // the following code to run on the main thread // but it will not. 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); } }

这个简单的控制台程序你就不用在家试了。这个程序是不会符合预期的,但同时也证实了以前说的那点。注意到我给主线程设置了一个Sync Context的对象。我建立了一个Sync Context实例,而后把它设置给当前的线程。这跟UI线程在建立form时所作的事情很是相像(不彻底同样,我稍后会解释。)。而后,我建立了一个线程Run1,并把主线程的sync context对象传递给它。当我尝试去调用Send的时候,我发现Send是在Run1线程里被调用而不是如咱们期待的同样在主线程调用。下面是输出:

Main thread is 10 Run1 Thread ID: 11 DoWork Thread ID:11

DoWork在线程11中被执行,这与线程Run1同样。没有SynchronizationContext到主线程。为何?到底发生了什么?经过这件事情,你应该意识到生活中没有什么是免费的。线程之间不能随意的切换,他们为了达到切换还须要一个基础设施。好比,UI线程用了一个message pump在它的SynchronizationContext对象中,它使消息同步到UI线程中。

所以,UI线程拥有本身的SynchronizationContext类,这个类集成于SynchronizationContext,叫System.Windows.Forms.WindowsFormsSynchronizationContext。这个类是一个与SynchronizationContext不一样的实现。UI版本重写了Post和Send方法,提供了一个关于这些方法的"message pump"版本(我尽力去找了这些类的实现,可是没有找到)。那么这个单纯的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); } } }

让咱们看下Send和Post的实现:

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方法只是简单的在当前线程调用了委托,而不是切换到另外一个线程,Post也是作了一样的事情,只是用了一个TreadPool来实现异步而已。我认为,这个类应当被定义为抽象的,这个默认的实现让人费解并且也没用。这也是我决定写这篇文章的缘由之一。

结论

我但愿你如今对这个class可以有了足够的了解,你能够弄懂怎么使用。在.net里面,我发现有两个类提供通常的同步功能。一个是Winform的线程上下文,另外一个是WPF的。我相信确定还有,但我目前只找到了这两个。这个类的默认实现没有实现从一个线程切换到另外一个线程。这也是一个简单的线程在默认的状况下不能有这样效果的缘由。另外一方面,UI线程有"message pump"和windows的api(好比SendMessage和PostMessage),所以我确信能够封送代码到UI线程。

然而,这不是对这个类的研究的重点。你能够本身实现一个SynchronizationContext类,这也很简单。实际上,我本身也写了一个。在个人工做中,咱们必须让全部基于COM的调用所有在STA的方法里运行。所以,我决定本身也一个版本的SynchronizationContext,名字叫StaSynchronizationContext。我将会在Part II部分展现给你们。

一些词语

"message pump":消息泵

后记

本篇翻译是起于在项目中要写到UI线程的回调,而又不想写Invoke,以前在别的项目中见到过这个写法,故拿来研究下,发现确实是个好东西,心动不已,so 给你们翻译下推广下此项技术。

原文连接

http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

相关文章
相关标签/搜索