(CLR via C# 3rd Edition)
在 GUI 程序中 (winform / wpf / silverlight),如果启动新的线程池线程,则在此线程中将不能直接更新 UI. 在 asp.net 中,在处理 client request 的线程中,同时会根据客户端的情况关联相应的 System.Globalization.CultureInfo, 以便服务器端能够使用客户相关的 culture-specific 的数字,日期和时间格式。而且,Web server 能够假定客户所用的身份(identify, 即 System.Security.Principal.IPrincipal), 以控制客户能够访问的资源。但如果在此线程中启动新的线程池线程,则此线程池线程将不再使用客户特定的 CultureInfo 和 Identity.
SynchronizationContext 类用于解决这些问题。其原型为:
public class SynchronizationContext { public static SynchronizationContext Current { get; } public virtual void Post(SendOrPostCallback d, object state); // 异步调用 public virtual void Send(SendOrPostCallback d, object state); // 同步调用 }
对 winform / wpf / silverlight 程序,GUI 线程具有一个相关的 SynchronizationContext 子类。对 winform 来说是 System.Windows.Forms.WindowsFormsSynchronizationContext, 对 wpf 和 silverlight 来说是 System.Windows.Threading.DispatcherSynchronizationContext.
可以通过 SynchronizationContext.Current 获得此对象。通常,会在发动线程池调用之前的某个时刻,将此对象的引用保存到另一个类变量或静态变量中。然后,在线程池中需要更新 UI 时,就可以通过调用其 Post 方法来实现。
推荐总是使用 Post 方法,而不要用 Send 方法。因为 Send 方法是同步调用,意味着在线程池线程中调用它时,会阻塞住当前线程,以等待其返回。而当线程池线程被阻塞时,往往会触发线程池调度程序分配一个新的线程,从而增加了不必要的资源耗费,降低性能。
从实现原理上说,WindowsFormsSynchronizationContext 的 Post 方法调用了 System.Windows.Forms.Control.BeginInvoke 方法,而 Send 方法则是调用了 Invoke 方法(分别对应于异步/同步调用)。类似的,对于 wpf 和 silverlight, System.Threading.DispatcherSynchronizationContext 的 Post 方法则是调用了 System.Windows.Threading.Dispatcher.BeginInvoke 方法,Send 方法调用其 Invoke 方法。
对 asp.net / xml webservices 程序而言,用于处理客户请求的那个线程池线程,会自动关联上一个 SynchronizationContext 类的子类。其中包含客户的 culture 和 identity 信息。该实例同样可以通过 SynchronizationContext.Current 得到。如果你要新启动一个线程,你同样可以先保存对当前 SynchronizationContext 对象的引用,然后晚一点通过 Post 方法在其上来执行代码,这些代码会在最初用来处理客户请求的同一个线程池线程中执行。
下面是一个封装,用来简化异步编程后需要在发起线程里执行任务(比如更新 UI)的场景。
private static AsyncCallback SyncContextCallback(AsyncCallback callback) { // 捕获调用者线程的同步对象 var sc = SynchronizationContext.Current; // 如果没有同步上下文,则不需要做任何包装,直接返回 if (sc == null) return callback; // 返回一个新的委托,当它被调用时,将原始调用转发 post 给捕获的同步上下文对象。并且传递原来的 IAsnycResult 参数。 return asyncResult => sc.Post(result => callback((IAsyncResult) result), asyncResult); }
调用例子:
protected override void OnMouseClick(MouseEventArgs e) { Text = "Web request initiated"; var req = WebRequest.Create(“http://rchen.cnblogs.com”); req.BeginGetResponse(SyncContextCallback(ProcessWebResponse), req); base.OnMouseClick(e); } void ProcessWebResponse(IAsyncResult result) { // 因为回调函数被包装过,这里可以直接更新 UI. var req = (WebRequest) result.AsyncState; using (var response = req.EndGetResponse(result)) { Text = "Context length: " + response.ContextLength; } }
==