C#线程间通信的异步机制
作者:springsnow
线程间通信
我们看下面的图
我们来看线程间通信的原理:线程(Thread B)和线程(Thread A)通信, 首先线程A 必须实现同步上下文对象(Synchronization Context), 线程B通过调用线程A的同步上下文对象来访问线程A,所有实现都是在同步上下文中完成的.线程B有两种方式来实现线程间的通信。
第一种:调用线程A的同步上下文对象,阻碍当前线程,执行红色箭头调用,直到黄色箭头返回(同步上下文执行完毕)才释放当前线程. (1->2->3->5)。
第二种:调用线程A的同步上下文对象(实际上是在开启一个新线程去执行,1->2->3->5) ,执行红色箭头,但并不阻碍当前线程(原有线程,1->4->5),绿色箭头继续执行。
文章中将会通过下面几个类来进行介绍:
- ISynchronizeInvoke 接口
- SynchronizationContext 类
- AsyncOperation / AsyncOperationManager 类
1. ISynchronizeInvoke 接口
我们先来看下面一段异步的代码(Window Form控件下有1个Button/1个Label),但点击Button的时候,执行异步调用,完成后,告诉Window Form的 Label控件Text属性”Asynchronous End”。
在windows应用窗体应用程序中,对窗体上控件属性的任何修改都必须在主线程中完成。不能从其他线程安全地访问控件的方法和属性。
ISynchronizeInvoke 接口来自.Net Framework 1.0,提供3个方法1个属性:
- BeginInvoke / EndInvoke 方法 : 异步方法
- Invoke 方法 : 同步方法
- InvokeRequired 属性 : 判读来源的执行线程
delegate void DoWork(); private void button1_Click(object sender, EventArgs e) { //更新状态,添加到Listbox 中 AddValue("Asynchronous Start."); //使用委托来调用异步方法 DoWork work = DoWorkMethod; work.BeginInvoke(OnWorkCallback, work); } void OnWorkCallback(IAsyncResult asyncResult) { DoWork work = asyncResult.AsyncState as DoWork; if (work != null) { work.EndInvoke(asyncResult); } //(1)方法:调用Control控件的Invoke //Action<string> asyncUpdateState = UpdateStatus; //Action<string> 介绍=> 附1 //Invoke(asyncUpdateState, "1:Asynchronous End."); //(2)方法:直接在异步调用的线程下 UpdateStatus("2:Asynchronous End."); } void UpdateStatus(string input) { //把你需要通知的控件Control 赋值给ISynchronizeInvoke //来实现线程间的通信 ISynchronizeInvoke async = this.listBoxStatus; //使用(1)方法,InvokeRequired == false ,来源当前(Window Form)主线程 if (async.InvokeRequired == false) AddValue(input); else// 使用(2)方法 == true ,来源其他线程(异步) { Action<string> action = new Action<string>(status => { AddValue(status); }); //调用ISynchronizeInvoke 提供的Invoke 同步方法,阻碍线程,直到调用结束 //也可以使用ISynchronizeInvoke 提供的异步BeginInvoke/EndInvoke方法来实现调用. async.Invoke(action, new object[] { input }); } } void AddValue(string input) { this.listBoxStatus.Items.Add(string.Format("[(#{2}){0}]Context is null:{1}", input, Thread.CurrentContext == null, Thread.CurrentThread.ManagedThreadId)); } void DoWorkMethod() { Thread.Sleep(3000);//模拟耗时工作 }
在代码中(UpdateStatus方法体内),我们可以看到主要是在ISynchronizeInvoke async = this.listBoxStatus;实现了线程间的通信,MSDN的解释” 实现此接口的对象可以接收事件已发生的通知,并且可以响应有关该事件的查询”. 并使Window Form(主线程) 下的ListBox 控件和来自异步方法(另外一个线程)的建立了通道。
InvokeRequired 判断线程的来源。
如果使用(1)方法,来源于Window Form 自身Control 的Invoke方法, InvokeRequired将返回false; 来源另外线程(异步)。
如果使用(2)返回true.同时ISynchronizeInvoke 提供了异步(BeginInvoke+EndInvok)和同步方法(Invoke)来实现线程间通信.Invoke 就是最上面的图1 所示的第一种 / BeginInvoke+EndInvok 是第二种。
2. SynchronizationContext 类
相比ISynchronizeInvoke 接口,SynchronizationContext 类(来自.Net Framework 2.0)提供了更多的方法来操作同步上下文对象,实现线程间通信.在上面的例子中SynchronizationContext类中将由 Post/Send 方法来实现。
反编译后我们看到:
public virtual void Post(SendOrPostCallback d, object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state); } public virtual void Send(SendOrPostCallback d, object state) { d(state); }
- Send = ISynchronizeInvoke 中的Invoke 同步调用.图1中的第一种
- Post = ISynchronizeInvoke 中的BeginInvoke + EndInvoke异步调用. 图1中的第二种
SynchronizationContext 类举例(在WinForm 下编程)
delegate void DoWork(); private void button1_Click(object sender, EventArgs e) { //System.Windows.Forms.Form 自动的创建默认的同步上下文对象, //直接的获取当前的同步上下文对象 SynchronizationContext context = SynchronizationContext.Current; //更新状态,添加到Listbox 中 AddValue<string>("Asynchronous Start."); //使用委托来调用异步方法 DoWork work = DoWorkMethod; work.BeginInvoke(OnWorkCallback, context); } void OnWorkCallback(IAsyncResult asyncResult) { AsyncResult async = (AsyncResult)asyncResult; DoWork work = (DoWork)async.AsyncDelegate; work.EndInvoke(asyncResult); //更新状态 UpdateStatus("Asynchronous End.", asyncResult.AsyncState); } void UpdateStatus(object input,object syncContext) { //获取主线程(Window Form)中同步上下文对象 SynchronizationContext context = syncContext as SynchronizationContext; //使用SynchronizationContext 类中异步Post 方法 SendOrPostCallback callback = new SendOrPostCallback(p => { AddValue<object>(p); }); context.Post(callback, input);//Post 为异步,Send 为同步 } void AddValue<T>(T input) { this.listBoxStatus.Items.Add(string.Format("[(#{2}){0}]Context is null:{1}", input, Thread.CurrentContext == null, Thread.CurrentThread.ManagedThreadId)); } void DoWorkMethod() { Thread.Sleep(3000);//模拟耗时工作 }
上面我们已经说过在主线程中System.Windows.Forms.Form 自动的创建默认的同步上下文对象, 这时候我们把当前的同步上下文对象通过参数的形式赋值到异步线程中,调用Post 方法来实现, Post 方法接收 SendOrPostCallback 委托和额外object state参数,在Post方法体内调用线程池的线程来实现(Code2.1).当然我们也可以直接使用Send方法。
下面我们看看线程中的代码(在Console 下编程)。
static class Program { static void Main() { Output("Main Thread Start."); //为主线程创建Synchronization Context var context = new SynchronizationContext(); //开始一个新线程 Thread threadB = new Thread(work); threadB.Start(context); Console.Read(); } static void work(object context) { Output("Thread B"); //获取主线程中的同步上下文对象 SynchronizationContext sc = context as SynchronizationContext; //异步的方式和主线程通信,并发送"Hello World". sc.Post(new SendOrPostCallback(p => { Output(p); }), "Hello World"); } static void Output(object value) { Console.WriteLine("[ThreadID:#{0}]{1}", Thread.CurrentThread.ManagedThreadId, value); } }
在主线程中因为没有同步上下文对象,所以开始我们new SynchronizationContext(); 对象,其他和上面基本一样.此例说明了Post 是开启新线程(线程池)运行的。
3. AsyncOperation / AsyncOperationManager 类
AsyncOperation / AsyncOperationManager 类是SynchronizationContext 类的进一步封装和实现, AsyncOperationManager在创建AsyncOperation对象的时候会取得当前线程的同步上下文对象,并存储在AsyncOperation之中,使我们访问同步上下文对象更加容易。
public class MySynchronizedClass { private AsyncOperation operation; public event EventHandler somethingHappened; public MySynchronizedClass() { //创建AsyncOperation 对象,并把当前线程的同步上下文保持到AsyncOperation中. operation = AsyncOperationManager.CreateOperation(null); Thread workerThread = new Thread(new ThreadStart(DoWork)); workerThread.Start(); } private void DoWork() { SendOrPostCallback callback = new SendOrPostCallback(state => { EventHandler handler = somethingHappened; if (handler != null) { handler(this, EventArgs.Empty); } }); operation.Post(callback, null); //注意1 operation.OperationCompleted(); } }
AsyncOperation类中实现了OperationCompleted的方法. SynchronizationContext 类中这个方法是没有具体的代码实现的。
到此这篇关于C#线程间异步通信的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。