首先,你要理解 Post 跟 Send. 具体可以去参考 Windows 的消息发送函数 SendMessage 跟 PostMessage,这里不做概述。
1. SynchronizationContext 对象
.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);
}
2. ISynchronizeInvoke 接口
未完待续。
3. AsyncOperation / AsyncOperationManager 类
有没有感觉 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);
}