线程间通信

  我们看下面的图

图1

  我们来看线程间通信的原理:线程(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”.

Code1.1

delegate void DoWork();private void button1_Click(object sender, EventArgs e){//辅助方法,查看当前线程Debug.WriteLine(string.Format("Window Form Method.Thread ID:#{0}",Thread.CurrentThread.ManagedThreadId));//Label lblStatus 属于主线程的控件[1]this.lblStatus.Text = "Asynchronous Start.";//使用委托来调用异步方法DoWork work = DoWorkMethod;work.BeginInvoke(OnWorkCallback, work);}void OnWorkCallback(IAsyncResult asyncResult){//辅助方法,查看当前线程Debug.WriteLine(string.Format("Asynchronous Callback Method.Thread ID:#{0}",Thread.CurrentThread.ManagedThreadId));DoWork work = asyncResult.AsyncState as DoWork;if (work != null){work.EndInvoke(asyncResult);}// 报错:"线程间操作无效: 从不是创建控件“lblStatus”的线程访问它."this.lblStatus.Text = "Asynchronous End"; //上面注释[1]
     }void DoWorkMethod(){Thread.Sleep(3000);//模拟耗时工作}

运行代码,我们在第22行报错(异步方法体内).为什么呢?我们必须清楚的一点,在windows应用窗体应用程序中,对窗体上控件属性的任何修改都必须在主线程中完成。不能从其他线程安全地访问控件的方法和属性。从Debug窗口中我们也可以看出(图1.1).执行Button Click事件的时候,运行在线程ID =#10; 在异步的方法体内,运行在线程ID=#7.不同线程间不能直接通信.

为了解决这个问题,实现图1.1 中 #10 和 #7 的通信,下来开始认识ISynchronizeInvoke 接口(此接口来自.Net Framework 1.0),提供3个方法1个属性:

  BeginInvoke / EndInvoke 方法 : 异步方法

  Invoke 方法 : 同步方法

  InvokeRequired 属性 : 判读来源的执行线程

  下面我们看Code1.2的具体代码来说明(对Code1.1改写,其中Label 改为ListBox)

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);//模拟耗时工作}

图1.2

  在代码中(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 是第二种.

  附1:关于Action<T…> / Func (T…, TResult) (简单的说就是”简化后的委托”)的知识可以看MSDN的介绍.

  Action<T…>: http://msdn.microsoft.com/zh-cn/library/018hxwa8.aspx

  Func (T…, TResult): http://msdn.microsoft.com/zh-cn/library/bb549151.aspx

  Code1.2虽然实现了线程间的通信, 回顾图1的解释,” 首先线程A 必须实现同步上下文对象(Synchronization Context)”, 而在Code1.2 中并没有为Window Form(主线程)实现上下文对象,如果没有这个对象一切都是不成立的.那么Window Form 做了些什么呢?

  我们来看下面的代码(使用Console程序):

  Code1.3

static class Program{static void Main(){//1,在Main 主线程中运行,查看线程ID和同步上下文Console.WriteLine("0.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//2,在Main 主线程中运行,实例化空对象Test,查看线程ID和同步上下文Test a = new Test();Console.WriteLine("1.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//3,在Main 主线程中运行,实例化FormTest对象(继承Form),查看线程ID和同步上下文FormTest test = new FormTest();Console.WriteLine("2.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);//4,在新线程中运行,查看线程ID和同步上下文new Thread(work).Start();Console.Read();}static void work(){Console.WriteLine("3.ThreadID:#{1},Synchronization Context is null?{0}",SynchronizationContext.Current == null, Thread.CurrentThread.ManagedThreadId);}}public class FormTest : System.Windows.Forms.Form { }public class Test { }

图1.3

  由代码和图可以看出(SynchronizationContext.Current == null 判断同步上下文对象是否存在), 实例化FormTest 对象后(继承System.Windows.Forms.Form),Form默认的帮我们创建了同步上下文对象,使主线程#9 具备了同步上下文对象,这就是为什么Code1.2 不用声明同步上下文对象的原因,同时也告诉我们,开启一个新线程#10,线程本身是没有同步上下文对象的.

  2. SynchronizationContext 类

  相比ISynchronizeInvoke 接口,SynchronizationContext 类(来自.Net Framework 2.0)提供了更多的方法来操作同步上下文对象,实现线程间通信.在上面的例子中SynchronizationContext类中将由 Post/Send 方法来实现.

  反编译后我们看到:

  Code2.1

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中的第二种

  改写Code1.2的代码(还是在WinForm 下编程).

  Code2.2

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 下编程).

  Code2.3

static class Program{static void Main(){Output("Main Thread Start.");//为主线程创建Synchronization Contextvar 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);}}

图2.3

  在主线程中因为没有同步上下文对象,所以开始我们new SynchronizationContext(); 对象,其他和Code2.2 基本一样.从图2.3很好的解释图1的第二种调用,也说明了Post 是开启新线程(线程池)运行的.

  3. AsyncOperation / AsyncOperationManager 类

  AsyncOperation / AsyncOperationManager 类是SynchronizationContext 类的进一步封装和实现, AsyncOperationManager在创建AsyncOperation对象的时候会取得当前线程的同步上下文对象,并存储在AsyncOperation之中,使我们访问同步上下文对象更加容易.

  Code3.1

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

代码很简单,我也不在解释,可以参照上面所有代码. 注意1: AsyncOperation类中实现了OperationCompleted的方法. SynchronizationContext 类中这个方法是没有具体的代码实现的.

  总结:

  文章中也非常适合线程的编程(除了异步)来实现通信, SynchronizationContext是最重要的一个,其他的扩展类,如SynchronizationContextSwitcher 等更高级的主题,具体可参考下面的链接. 在Winform中有个非常重要的BackgroundWorker 类,关于BackgroundWorker的文章很多,在此不做解释了.(http://hi.baidu.com/ldy201001/blog/item/3ea946c4a586f8a08326ac1a.html)

本文转自:http://www.cnblogs.com/cpcpc/archive/2012/07/02/2572711.html

相关:AsyncCallback方法和主线程怎么同步呢?

转载于:https://www.cnblogs.com/jRoger/articles/2574522.html

Net线程间通信的异步机制相关推荐

  1. 【Android】线程间通信——Handler消息机制

    文章目录 引言 Java层 永动机跑起来 示例 Looper Handler MessageQueue 永动机停下 Native层 nativeInit() nativePollOnce() nati ...

  2. android线程间通信的几种方法_Android 技能图谱学习路线

    Java基础 Java Object类方法 HashMap原理,Hash冲突,并发集合,线程安全集合及实现原理 HashMap 和 HashTable 区别 HashCode 作用,如何重载hashC ...

  3. 线程间通信: Handler , Looper, MessageQueue, Message (完结)

    概述:    为了 线程间 通信方便, Handler 机制 通过 Handler 和 Looper, MessageQueue, Message 这些 类 之间的协作, 简化 多线程的开发.  线程 ...

  4. Java多线程编程-(4)-线程间通信机制的介绍与使用

    上一篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

  5. 【java笔记】线程间通信(1):等待唤醒机制

    线程间通信概念:多个线程处理同一个资源,但是处理的动作却不相同 必要性:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当需要多个线程来共同完成一件任务,并且希望它们有规律的执行,那么多线程之 ...

  6. android线程间通信的几种方法_Android线程间通信机制

    讲解Handler机制的博文很多,我也看了很多,但说实话,在我对Handler几乎不怎么了解的情况下,每一篇文章我都没太看懂,看完之后脑子里还是充满了疑问.究其原因,是因为几乎每一篇文章一上来就开始深 ...

  7. Android线程间通信机制

    Android线程间通信机制 当android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进 ...

  8. linux线程间通信优点,进程间通信与线程间通信【转】

    一个进程写管道:写入字节数小于PIPE_BUF是原子操作,写操作在管道缓冲区没有及时读走时发生阻塞. 一个进程读管道:读操作在管道缓冲区没有数据时发生阻塞. 以前一直想找个机会总结一下进程和线程的通信 ...

  9. Java 多线程(六)——进程间通信与线程间通信

    以前一直想找个机会总结一下进程和线程的通信机制,但由于技术和平台的局限性,一直没有找准切入点.由于马上要毕业了,对自己技术的总结和梳理的前提下写了本篇文章,如有错误之处,敬请拍砖和指教. 操作系统的主 ...

最新文章

  1. CEGUI中文处理(补)
  2. Xcode中导入.a静态库后报错添加-force_load或-all_load
  3. 2021年新高考八省联考成绩查询福建,2021福建八省联考成绩查询时间
  4. Compound供应量突破70亿美元
  5. nginx过滤post请求头_Nginx Header,实现对HTTP/S请求、响应进行添加、修改、删除等操作...
  6. 微信 8.0 来啦,炸裂!
  7. java编解码技术,netty nio
  8. 课题申报书范文_课题优秀申报书 课题申报书范例
  9. 第四周 特殊应用:人脸识别和神经风格转换(Special applications: Face recognition Neural style transfer)
  10. 多个数的最小公倍数求法
  11. Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies 问题解决
  12. 【数据库】PostgreSQL简介
  13. 域名抢注哪个通道成功率高?价格贵不贵?
  14. opencv打开摄像头失败的一种可能cv2.error: Unknown C++ exception from OpenCV code
  15. excel怎么设置打印区域_excel:将多个表格的不同区域打印在一张纸上
  16. Autodesk Inventor: Accelerating Design Using Standards Autodesk Inventor教程之利用标准加速设计过程 Lynda课程中文字幕
  17. 自然语言处理中注意力机制综述
  18. Python第一阶段学习 day14
  19. Stm32嵌入式电子相册简易实现
  20. tftp 服务器搭建测试版本ubuntu14.04

热门文章

  1. A free SSH client - putty[]
  2. Gitlab环境快速部署(RPM包方式安装)
  3. 用border-width,border-color画三角形
  4. 【转】在python下使用包progressbar控制进度条
  5. Web端a标签跳转地图等链接(收藏)
  6. nginx 同一个端口支持 http https_Nginx
  7. 14.PHP_PHP与XML技术
  8. POJ2752KMP逆序处理
  9. POJ2226 不错的最小顶点覆盖
  10. 【数字信号处理】线性常系数差分方程 ( 使用 matlab 求解 “ 线性常系数差分方程 “ 示例 | A 向量分析 | B 向量分析 | 输入序列分析 | matlab 代码 )