首先,你要理解 Post 跟 Send. 具体可以去参考 Windows 的消息发送函数 SendMessage 跟 PostMessage,这里不做概述。
.Net 线程通信,主要使用的是 SynchronizationContext 对象。此对象用法很简单。
首先我们看窗口程序代码:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { test(); } static void test() { Output("Main Thread"); // 获取当前线程的 SynchronizationContext 对象 SynchronizationContext sc = SynchronizationContext.Current; Action act = work; act.BeginInvoke(Work_CB, sc); } static void work() { Thread.Sleep(1000); } static void Work_CB(IAsyncResult ar) { Output("Worker Thread"); // 获取主线程中的同步上下文对象 SynchronizationContext sc = ar.AsyncState as SynchronizationContext; // 异步的方式和主线程通信. sc.Post(p => { Output(p); }, "which thread exec me ?"); } static void Output(object value) { string str = string.Format("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value); MessageBox.Show(str); } }
执行结果:
[ThreadID:9] Main Thread
[ThreadID:10] Worker Thread
[ThreadID:9] Which thread exec me ?
怎么样,得到我们预期的结果,Worker线程中调度Main线程执行了代码。这就是 SynchronizationContext 的最基本用法。
现在我们再看控制台下:
// 控制台代码 class Program { static void Main(string[] args) { test(); System.Console.Read(); } static void test() { Output("Main Thread"); // 获取当前线程的 SynchronizationContext 对象 SynchronizationContext sc = SynchronizationContext.Current; Action act = work; act.BeginInvoke(Work_CB, sc); } static void work() { Thread.Sleep(1000); } static void Work_CB(IAsyncResult ar) { Output("Worker Thread"); // 获取主线程中的同步上下文对象 SynchronizationContext sc = ar.AsyncState as SynchronizationContext; // 异步的方式和主线程通信. sc.Post(p => { Output(p); }, "which thread exec me ?"); } static void Output(object value) { Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value); } }
编译执行。你发发现,同样的代码,在控制台下运行,程序崩溃。
调试,发现 sc 为 NULL,即 SynchronizationContext.Current 为 NULL。 既然这样,那我们就想办法,自己 New 一个 SynchronizationContext对象。
SynchronizationContext sc = new SynchronizationContext();
编译执行,结果:
我们发现,Woker线程无法调度Main线程去执行Output()函数。
这是为界面程序可以,而控制台程序不可以?
天下没有白吃的午饭,线程间能够通信,肯定也要付出代价,这个代价就是windows消息循环。当你在控制台程序里,创建了任意一个控件对象时,一个 SynchronizationContext 派生对象就会随之被之创建,被附着在创建控件对象的线程中。这样,在这个线程中调用SynchronizationContext.Current,我们就可以得到线程间通信的钥匙。当此线程的消息循环(除了消息循环,还有其他设计)被创建,我们就能通过钥匙来进行通信了。
这时候如果你了解了SendMessage和PostMessage,你就能有个详细的了解。
如果SynchronizationContext不做派生,那默认的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); }
未完待续。
有没有感觉 SynchronizationContext 对象用的不舒服?由于Woker 线程没有与之关联的 SynchronizationContext 对象,我们的 test() 函数,还需要重新设计,不然还无法在控制台下运行。
重新设计的代码:
static void test() { Output("Main Thread"); // 不在需要了 // SynchronizationContext sc = SynchronizationContext.Current; Action act = work; // 包装一下 act.BeginInvoke(SyncContextCallback(Work_CB), null); } static void work() { Thread.Sleep(1000); } static void Work_CB(IAsyncResult ar) { Output("Worker Thread"); // 也不在需要了 // SynchronizationContext sc = ar.AsyncState as SynchronizationContext; // sc.Post(p => { Output(p); }, "which thread exec me ?"); Output("which thread exec me ?"); } static void Output(object value) { Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value); } // 此函数将一个普通的AsyncCallback方法转换成特殊的AsyncCallback 方法 // 它通过SynchronizationContext 来调用。这样无论线程模型中是否含有GUI线程,都可以正确的调用。 private static AsyncCallback SyncContextCallback(AsyncCallback callback) { SynchronizationContext sc = SynchronizationContext.Current; if (sc == null) return callback; return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult); }
可以把这段代码用UI线程和非UI线程都调用一遍,发现都能正常运行。UI线程调用,回调函数也运行在UI线程;非UI线程调用,.NET从线程池中取出一个线程执行回调函数。
SyncContextCallback() 函数其实就是AsyncOperation的基本原理。
附上 AsyncOperation/AsyncOperationManager 基本的使用代码:
static void test() { Output("Main Thread"); AsyncOperation operation = AsyncOperationManager.CreateOperation(null); Action act = work; act.BeginInvoke(Work_CB, operation); } static void work() { Thread.Sleep(1000); } static void Work_CB(IAsyncResult ar) { Output("Worker Thread"); AsyncOperation operation = ar.AsyncState as AsyncOperation; operation.Post(p => { Output(p); }, "which thread exec me ?"); } static void Output(object value) { Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value); }