C# 异步编程基础(四) 富客户端应用程序的线程 和 同步上下文 Synchronization Contexts

此入门教程是记录下方参考资料视频的过程
开发工具:Visual Studio 2019

参考资料:https://www.bilibili.com/video/BV1Zf4y117fs

目录

C# 异步编程基础(一)线程和阻塞

C# 异步编程基础(二)线程安全、向线程传递数据和异常处理

C# 异步编程基础(三)线程优先级、信号和线程池

C# 异步编程基础(四) 富客户端应用程序的线程 和 同步上下文 Synchronization Contexts

C# 异步编程基础(五)Task

C# 异步编程基础(六)Continuation 继续/延续 、TaskCompletionSource、实现 Task.Delay

C# 异步编程基础(七)异步原理

C# 异步编程基础(八) 异步函数

C# 异步编程基础(九) 异步中的同步上下文、ValueTask

C# 异步编程基础(十) 取消(cancellation)、进度报告、TAP(Task-Based Asynchronous Pattern)、Task组合器

富客户端应用程序的线程

  1. 在WPF、UWP、WinForm等类型的程序中,如果在主线程执行耗时的操作,就会导致整个程序无响应。因为主线程同时还需要处理消息循环(事件循环),而渲染和鼠标键盘事件处理等工作都是消息循环来执行的
  2. 针对这种耗时的操作,一种流行的做法是启用一个worker线程
    执行完操作后,再更新到UI
  3. 富客户端应用的线程模型通常是:
    UI元素和控件只能从创建它们的线程来进行访问(通常是主UI线程)
    当想从worker线程更新UI的时候,你必须把请求交给UI线程
  4. 比较底层的实现是:
    WPF,在元素的Dispather对象上调用BeginInvoke或Invoke
    WinForm中,调用控件的BeginInvoke或Invoke
    UWP,调用Dispatcher对象上的RunAsync或Invoke
  5. 所有这些方法都接收一个委托
  6. BeginInvoke或RunAsync通过将委托排队到UI线程的消息队列来执行工作
  7. 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

  1. 在System.ComponentModel下有一个抽象类,SynchronizationContext,它使得Thread Marshaling得到泛化
    Marshaling:将网站的数据移动到其它语言编写的网站里,需要把数据转化成可发送的数据格式,例如C#数据转化为JSON叫Marshaling,其它语言将JSON数据转化为自己的数据类型叫UnMarshaling
    Thread Marshaling:将一个线程的数据的所有权交给另外一个线程
  2. 针对移动、桌面(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() 方法
}

富客户端应用程序的线程 和 同步上下文 Synchronization Contexts 结束

posted @ 2021-02-07 16:30  .NET好耶  阅读(768)  评论(0编辑  收藏  举报