C#线程间通信的异步机制
作者:springsnow 发布时间:2023-10-13 16:45:23
线程间通信
我们看下面的图
我们来看线程间通信的原理:线程(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 类中这个方法是没有具体的代码实现的。
来源:https://www.cnblogs.com/springsnow/p/11195710.html


猜你喜欢
- 通过路径从磁盘直接读取图片这段时间在做Springboot和Vue的例子,读取图片给出路径直接可以读,太方便了,一直么有搞懂为什么。后面看到
- Chart折线图使用鼠标滚轮放大、缩小和平移曲线使用鼠标滚轮滚动放大和缩小X轴的宽度,鼠标左键按住拖动实现曲线的左右平移,不再使用滚动条。添
- 在程序运行中经常需要对数据进行对比显示,其中使用柱状图显示非常直观,可以更显著的比较出数据量的走势。下面介绍在C#中柱状图的制作方法:1、方
- 需求描述现在有这样一个需求:我有A、B两台服务器,其中A是一个视频处理服务器,B是一个数据存储服务器。此时有一个视频需要先在A服务器上进行一
- 本文实例讲述了java实现mp3合并的方法。分享给大家供大家参考。具体实现方法如下:package test;import java.io.
- 这篇文章主要介绍了Spring Boot2.X国际化文件编写配置,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- 本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下:一、多重继承
- 前言上一篇我们介绍了使用 sqflite 这个数据库工具在 Flutter 的应用中建立本地数据库的实例应用。了解过数据库的同学应该会知道,
- 一、基本介绍(Nexus(maven * ))1,如果没有搭建 * 会有什么问题?如果没有 * ,我们所需的所有构件都需要通过 Mave
- 前言Java17将是一个长期支持的LTS版本。Java采用了6个月的发布周期。也就是说,它将每6个月发布一个新版本的Java。每隔3年,LT
- 定时/计划功能主要使用的就是Timer对象,它在内部还是使用多线程的方式进行处理,所以它和线程技术还是有非常大的关联。Timer类主要作用就
- 一、可空类型修饰符(?)C#2.0里面实现了Nullable数据类型//A.比如下面一句,直接定义int为null是错误的,错误提示为无法将
- 学生模块功能比较少,就是进行考试和查看自己成绩两个大的功能。学生进行考试的功能比较复杂(首先做了校验,不在考试时间范围内,不能进行考试)考试
- 引用类型包含值类型字段,引用类型初始化后,值类型默认会被初始化为0、Null。 CLR允许为值类型定义构造器,但是构造器的调用,就必须显式的
- 什么事读写分离读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELEC
- 创建新的项目的时候,文件名一直追加,不分层对于刚用idea的小白,这个问题困扰了我好几天了,幸好现在还不怎么敲代码,下面给一个详细的解决方案
- 文件上传的方法主要目前有两个常用的,一个是SmartUpload,一个是Apache的Commons fileupload.我们这里主要介绍
- AsyncTask,顾名思义,异步任务。说到异步,最简单的理解就是不同步。再复杂一点理解,就得举例子了。假设我要去火车站买票,刚到火车站我突
- sms4j 2.0 全新来袭即sms-aggregation成功加入dromara之后,很多人向我们反应了项目名称太长不好记,也太绕口, 在
- 功能实现:1、图片加载类ImageLoader实现:1)用阻塞队列存储要图片:BlockingQueue images = new Arra