最近在研究.net4.5中的有关异步编程的新特性,从自己Google Reader 订阅的一些博客中接触到了两个很陌生的单词ExecutionContext和SynchronizationContext,于是仔细研究了一下,记录下来备忘。
什么是ExecutionContext在许多系统中thread-local storage(TLS)线程本地存储记录了正在运行的当前环境或者上下文的一些信息,而在CLR中ExecutionContext做了类似的事情.在同步的世界中,所有的一切都发生在当前线程内,线程内有关的所有数据对其内所有代码可见,也同时被所有代码所操纵影响.也就是说假设有方法ABC在当前线程内顺序执行,当前线程环境的一切信息对ABC都是可见的,同时ABC又影响着这些当前线程环境.但是在异步的世界情况变了,还假设有方法ABC,这三个方法有可能运行在不同的线程中,ABC三者是相互见不到对方线程信息的,所以需要在每个异步点传递当前线程环境信息到另一个线程中,另一个线程中的方法才能访问该线程中数据信息并且修改这些信息.完成这个任务就需要ExecutionContext记录当前线程环境然后再另一个线程中restore出来. 情况类似于这样:// 记录当前的环境信息到ec, 这些信息会在委托调用时通过静态的方法Run恢复出来ExecutionContext ec = ExecutionContext.Capture(); ExecutionContext.Run(ec, delegate { … // 在这的代码就能看见ec中的环境信息了。}, null);
在.Net FrameWork中所有处理异步情况的方法都会通过类似的方法Capture/Restore ExecutionContext(不包括unsafe代码),比如Task.Run,ThreadPool.QueueUserWorkItem,Delegate.BeginInvoke,Stream.BeginRead,DispatcherSynchronizationContext.Post
比如Task.Run,当我们调用Run时,背后所做的事就是ExecutionContext.Capture()捕获当前线程环境,然后在Run中的delegate方法执行的时候把环境还原。
什么是SynchronizationContext
程序员都很热衷与抽象,对硬编码深恶痛绝,分层和抽象会为我们日后维护和扩展系统带来极大的便利,这也就是为什么会有接口,抽象类,和虚方法。而SynchronizationContext也是一种抽象。打个比方,当我们编写WinForm程序时,为避免后台处理海量数据带来的UI界面假死状态,通常会重开线程或者将这些工作放入线程池等,任务完成之后把处理的结果封送返回给UI线程时,通常会用Control.BeginInvoke方法。但是这种情况如果发生在WPF和Silverlight程序中呢,封送处理又得用Dispatcher.BeginInvoke或者InvokeAsync.ASP.Net又是另一种情况。于是我们使用了不同了方法完成了相同的动作,那么对于不同的UI框架我们怎么处理这些相同事件呢?于是抽象的想法来了,这就是SynchronizationContext。它提供了一个虚方法Post(),这个方法会接收委托方法并且执行它。而对于WinForm的情况会提供WinFormSynchronizationContext,它会覆盖掉虚方法Post(),并且在子类中Post()调用Control.BeginInvoke;对于WPF或者Silverlight,会提供子类DispatcherSynchronizationContext,同样覆盖掉虚方法Post(),并且在子类Post中调用Dispatcher.Run。
public static void DoWork(Control c) { ThreadPool.QueueUserWorkItem(delegate { .....c.BeginInvoke(delegate { .....//对UI的操作 }); }); }如果替换成SynchronizationContext去写就成了这样:public static void DoWork(SynchronizationContext sc) { ThreadPool.QueueUserWorkItem(delegate { … // do work on ThreadPool sc.Post(delegate { … // do work on UI }, null); }); } 或者如下public static void DoWork() { var sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem(delegate { … // do work on ThreadPool sc.Post(delegate { … // do work on the original context }, null); }); }
ExecutionContext VS SynchronizationContext
对比两者你会发现它们都会在异步操作时捕获当前线程环境上下文,但后续操作不一样,ExecutionContext会调用delegate方法并在执行时在另一个线程中恢复该线程环境状态。而SynchronizationContext仅仅是用当前捕获的环境信息去调用委托方法,这个委托方法怎么什么时候执行,在哪执行,怎么执行都取决于底层的具体实现。
注意:
以下CodeProject文章中有一点讲得不太准确,根据惯例,如果一个线程的当前 SynchronizationContext 为 null,那么它隐式具有一个默认 SynchronizationContext。默认 SynchronizationContext 将其异步委托列队到 ThreadPool,但在调用线程上直接执行其同步委托。因此,其上下文包含所有 ThreadPool 线程以及调用 Send 的任何线程。此上下文“借用”调用 Send 的线程,将它们放入其上下文,直至委托完成。从这种意义上讲,默认上下文可以包含进程中的所有 线程。所以UI应用程序通常有两个同步上下文,一个是UI线程的SynchronizationContext,另一个是包含ThreadPool线程的默认SynchronizationContext。
一般遇到疑问,首先去百度国内论坛,但是继而回去CodeProject相关文章去佐证,好多文章都是从Codeproject上翻译或者浓缩出来的,但这次事件我才发现CodeProject上文章也不靠谱,虽然那上边有好多微软MVP和其他大公司的技术人员,微软的东西还是得去MSDN blog或者 MSDN Magzine上比较靠谱,尤其是看到Anders Hejlsberg, Stephen Toub, Erick Lippert, Herb Sutter审阅,就知道这是质量的保证。备忘完毕,还有半天时间,接着去深挖这块东西。
reference: