C# 异步编程基础(四) 富客户端应用程序的线程 和 同步上下文 Synchronization Contexts
此入门教程是记录下方参考资料视频的过程
开发工具:Visual Studio 2019
目录
C# 异步编程基础(六)Continuation 继续/延续 、TaskCompletionSource、实现 Task.Delay
C# 异步编程基础(十) 取消(cancellation)、进度报告、TAP(Task-Based Asynchronous Pattern)、Task组合器
富客户端应用程序的线程
- 在WPF、UWP、WinForm等类型的程序中,如果在主线程执行耗时的操作,就会导致整个程序无响应。因为主线程同时还需要处理消息循环(事件循环),而渲染和鼠标键盘事件处理等工作都是消息循环来执行的
- 针对这种耗时的操作,一种流行的做法是启用一个worker线程
执行完操作后,再更新到UI - 富客户端应用的线程模型通常是:
UI元素和控件只能从创建它们的线程来进行访问(通常是主UI线程)
当想从worker线程更新UI的时候,你必须把请求交给UI线程 - 比较底层的实现是:
WPF,在元素的Dispather对象上调用BeginInvoke或Invoke
WinForm中,调用控件的BeginInvoke或Invoke
UWP,调用Dispatcher对象上的RunAsync或Invoke - 所有这些方法都接收一个委托
- BeginInvoke或RunAsync通过将委托排队到UI线程的消息队列来执行工作
- Invoke执行系统的操作,但随后会进行阻塞,直到UI线程读取并处理消息
因此,Invoke允许您从方法中获取返回值
如果不需要返回值,BeginInvoke/RunAsync更可取,因为它们不会阻塞调用方,也不会引入死锁的可能性
例子,一个WPF程序,点击按钮会假死
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Work();
}
private void Work()
{
//Thread.Sleep(5000),相当于执行一个耗时操作
Thread.Sleep(5000);
//修改TextBox的值
this.TextMessage.Text = "The answer";
}
例子,不会假死,但是会有异常
this.TextMessage.Text = "The answer";
会抛出System.InvalidOperationException:“调用线程无法访问此对象,因为另一个线程拥有该对象。”
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(this.Work).Start();
}
private void Work()
{
//Thread.Sleep(5000),相当于执行一个耗时操作
Thread.Sleep(5000);
//修改TextBox的值
this.TextMessage.Text = "The answer";
}
例子,不会假死,也不会抛出异常
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(this.Work).Start();
}
private void Work()
{
//Thread.Sleep(5000),相当于执行一个耗时操作
Thread.Sleep(5000);
this.UpdateMessage("The answer");
}
void UpdateMessage(string message)
{
//修改TextBox的值
Action action = () => this.TextMessage.Text = message;
//Dispatcher.BeginInvoke(action)相当于排队把委托发送到UI线程的消息队列里
Dispatcher.BeginInvoke(action);
}
同步上下文 Synchronization Contexts
- 在System.ComponentModel下有一个抽象类,SynchronizationContext,它使得Thread Marshaling得到泛化
Marshaling:将网站的数据移动到其它语言编写的网站里,需要把数据转化成可发送的数据格式,例如C#数据转化为JSON叫Marshaling,其它语言将JSON数据转化为自己的数据类型叫UnMarshaling
Thread Marshaling:将一个线程的数据的所有权交给另外一个线程 - 针对移动、桌面(WPF、UWP、WinForms)等富客户端应用的API,它们都定义和实例化了SynchronizationContext的子类
可以通过静态属性SynchronizationContext.Current来获得(当前运行在UI线程时)
捕获该属性让你可以在稍后的时候从worker线程向UI线程发送数据
调用Post就相当于diaoyDispatch或Control上面的BeginInvoke方法
还有一个Send方法,它等价于Invoke方法
例子
SynchronizationContext _uiSyncContext;
public MainWindow()
{
InitializeComponent();
//为当前UI线程捕获Synchronization Context
this._uiSyncContext = SynchronizationContext.Current;
new Thread(this.Work).Start();
}
private void Work()
{
Thread.Sleep(5000);
this.UpdateMessage("The answer");
}
void UpdateMessage(string message)
{
//把委托 Marshal 给UI线程
this._uiSyncContext.Post(_ => this.TextMessage.Text = message, null);
//调用 Post() 就相当于调用 Dispatch 或 Control 上面的 BeginInvoke() 方法
}