C# .NET 使用SynchronizationContext访问主线程

项目场景:

项目场景:WindowsFrom或WPF项目中不方便使用到控件的Invoke方法时,可以使用WindowsBase.dll下的System.Thread.SynchronizationContext


问题描述

很多人不了解什么是主线程,即具有Context的线程,他的设计是为了提高用户与屏幕的交互效率,进而提高用户体验。


原因分析:

有时候我们开发的桌面UI应用莫名卡顿了,一般是其他数据来源属于其他线程,但我们使用其他线程操作UI时就会出现卡顿的情况。


解决方案:

具体解决方案:

  • 首先定义可同步的主线程SynchronizationContext 上下文引用介质
SynchronizationContext SyncContext = null;
  • 第二步:在需要的地方使用上下文引用介质SyncContext 的Post方法
protected void initPortReceive()
        {
    
    
            SyncContext.Post(change, "Subscription receiving catch error");
        }
  • 第三步:UI中显示数据
private void change(object str)
        {
    
    
            showTips(str.ToString());
        }

桌面应用运行效果如下:
在这里插入图片描述
测试运行36小时,未发生异常.且软件效果流畅。

原理解析

概念

​ 在 .NET 框架的多线程程序中,往往很多时间需要将一个线程工作单元或上下文,传递给另一个线程。我们都知道的是 Windows 上的程序是以 消息循环为中心的,这个如何理解呢?

每一个 window 窗体都有一个与之关联的 Window Procedure

这个是一个用来处理所有消息发送或发送到类的给所有消息的函数。窗体的所有UI显示和显示都取决于 Window Procedure 对这些消息的响应。

SynchronizationContext 主要提供了一下三方面的功能:

  • 提供了一种把工作单元 添加到 上下文的队列中的方法。

  • 每一个线程 都有 一个 “current” 的 上下文。

  • 它保存着未完成异步操作数的计数

计数

这个数量 随着 当前的 SC 被捕获,或被夺取时增加,当捕获的 SC 用于将完成通知排队发送到该上下文时,则减少

重要的 SynchronizationContext APIClass

{
    
    
  // Dispather work to the context.
  void Post(); // Asynchronously
  void send(); // Synchronously
  
  // Keep track of the number of asynchronous operations.
  void OperationStarted();
  void OperationCompleted();
  
  // Each thread has a current context.
  // If "Current" is null, then the thread's current context is
  // "new SynchronizationContext()", by convention.
  static SynchronizationContext Current{
    
    get;}
  static void SetSynchronizationContext(SynchronizationContext);
}

SynchronizationContext 的实现

WindowsFormsSynchronizationContext (System.Windows.Forms.dll)

  • Use ISynchronizeInvoke on UI Control,用来将委托传递 给 win32 message loop

  • 每一个 UI 线程 都会创建一个 WindowsFormsSynchronizationContext

  • WindowsFormsSynchronizationContext 的上下文是一个 单一的UI 线程

DispatcherSynchronizationContext(WindowsBase.dll: System.Windows.Threading)

  • 以 “Normal” 优先级的委托 传递给 UI 线程。

  • 所有排队到 DispatcherSynchronizationContext 的委托都是由 特定的UI线程 按照他们排队的顺序 依次执行,一次执行一个。

  • DispatcherSynchronizationContext 的上下文是一个 单一的 UI 线程

Default(ThreadPool) SynchronizationContext(mscorlib.dll: System.Threading)

  • 默认的 SynchronizationContext 是一个默认构造函数的 SynchronizationContext 对象,按照惯例,如果一个线程 当前的 SynchronizationContext 是 null,那么它会 隐式的 含有一个 默认的 SynchronizationContext。

  • 默认 SynchronizationContext 将它的异步委托 添加到 线程池 队列,但是 在调用的线程上执行它的同步委托。因此,它的上下文 涵盖了 所有的线程池的线程 以及 调用它的线程。上下文 “借用” 调用它的线程,然后把它们带入到上下文中 直到委托结束。从某种意义上来说,默认上下文可能包含当前进程中的任何线程。

  • 默认 SynchronizationContext 是应用在 线程池中的线程的除非是被 ASP.NET 托管的代码,默认的 SynchronizationContext 也隐式应用于显式的子线程中除非子线程设置了自己的 SynchronizationContext。因此,UI 的应用一般都有两个 SynchronizationContext, UI 的 SynchronizationContext cover UI thread, default SynchronizationContext cover ThreadPool thread。


在这里插入图片描述


图1 是一个典型比如 WPF 程序,调用 Dispatcher.Invoke 或 Dispatcher.BeginInvoke 时Context 转换的一个图。

private void On_Time_Elapsed(object sender, EventArgs e)
{
    
    
		Dispatcher.Invoke(()=>{
    
    
      _displayTextBlock.Text = "Show Here.";
    });  
}

在这里插入图片描述


图2 是 WPF 程序中,Dispatcher.Invoke 中又新开了线程池的线程执行的例子。

private void On_Time_Elapsed(object sender, EventArgs e){
    
    
  Dispatcher.Invoke(()=>{
    
    
 			Task.Run(()=>{
    
    
        // Do Something here.
      });   
    
    _displayTextBlock.Text = "1111";
  });
}

猜你喜欢

转载自blog.csdn.net/gao511147456/article/details/128283228
今日推荐