SynchronizationContext 学习

(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;
	}
}

==

posted on 2010-09-05 18:17  NeilChen  阅读(2640)  评论(0编辑  收藏  举报

导航