一、同步上下文(SynchronizationContext)概述:
1、概念
同步上下文:这里同步是动词,据有归类的功能,假如有A(UI线程)、B类两线程,B线程要更新A线程的内容。如果直接在B中更新A中内容,那就是B线程多管闲事了,增加程序的耦合。为了降低程序的耦合度,B线程必须把更新A线程UI的事情还给A线程, 以消息方式把
要更改内容发送给A线程,A线程有一个堆栈用来保存B线程发送过来的消息。然后A线程根据自己情况决定什么时候更新。
如果B线程以Send()方法给A线程发送消息,B线程发送消息后什么事情都不做一直等待A线程的回复(同步),对应SynchronizationContext.Send()方法。
如果B线程以Post()方法给A线程发送消息,B线程发送完消息后就去做其他事情了(异步)),对应SynchronizationContext.Post()方法(asp.net 除外)。
同步上下文是一种可以将工作单元(执行某些方法 多播委托)排队到上下文(主要是不同的线程)的方法。
它的作用通俗来讲就是实现线程之间通讯的。
同步上下文应用于很多场景,比如在WinForms和WPF中,只有一个UI线程可以更新UI元素(文本框,复选框等)。如果尝试从另一个非UI线程更改文本框的内容,则不会发生更改,也可能抛出异常(取决于UI框架)。因此,在这样的应用程序中,非UI线程需要将对UI元素的所有更改安排到UI线程。这就是同步上下文提供的内容。它允许将一个工作单元(执行某些方法)发布到不同的上下文 - 在这种情况下是UI线程。
2、作用域
无论是什么平台(ASP.NET、Windows 窗体、Windows Presentation Foundation (WPF)、Silverlight 或其他),所有 .NET 程序都包含 SynchronizationContext 概念。
Microsoft .NET Framework提供了同步上下文的SynchronizationContext类。根据平台框架不同,又单独提供了WindowsFormsSynchronizationContext(WinForm)类、DispatcherSynchronizationContext(WPF)类等同步上下文的模型但都是继承自SynchronizationContext类。
每个线程都有一个默认的SynchronizationContext,但是不是每个线程都附加SynchronizationContext.Current这个对象,只有UI线程是一直拥有的SynchronizationContext.Current。故获取SynchronizationContext.Current也只能在UI线程上进行SynchronizationContext context = SynchronizationContext.Current;
//只有UI线程能获取到 值 SynchronizationContext maincontent= SynchronizationContext.Current; //asp.net 和控制获取的结果是null SynchronizationContext maincontent= SynchronizationContext.Current;
3、原理
通过UI线程与工作线程的时序图可以看出整个更新的步骤:
整个过程中关键的是主线程的SynchronizationContext,SynchronizationContext在通讯中充当传输者的角色。在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。
4、作用:传输者
传输者SynchronizationContext在通讯中充当传输者的角色(用Post/Send方法实现传输),实现功能就是一个线程和另外一个线程的通讯。SynchronizationContext将UI线程的同步环境保存下来,让这个环境可以在不同的线程之间流动,其他非UI线程可以用这个环境回到要ui线程执行的任务。例如在winform应用中,非UI线程中利用这个环境更新UI控件的内容,而不是利用控件的invoke方法。 SynchronizationContext.post()表示启用一个新线程来执行委托(异步执行)。SynchronizationContext.send()表示在当前线程执行(同步的)。SynchronizationContext.post是同步上下文最重要的一个方法。
Send:发送界面更新请求至主线程,阻塞当前线程直至返回。SynchronizationContext.Send(SendOrPostCallback d,object state):
Post:发送界面更新请求至主线程,不阻塞当前线程。SynchronizationContext.Post(SendOrPostCallback d,object state);
public
delegate void SendOrPostCallback(object state);d
为一个没有返回值,并且具有一个Object类型传入参数的委托(SendOrPostCallback );state
为执行这个委托时的参数(object);
注意:
SynchronizationContext的对象不是所有线程都被附加的,只有UI主线程会被附加。
对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?
在Form1 form = new Form1()之前,SynchronizationContext对象是为空,而当实例化Form1窗体后,SynchronizationContext对象就被附加到这个线程上了。
所以可以得出答案了:当Control对象被创建的同时,SynchronizationContext对象也会被创建并附加到线程上。所以在使用时,一定要等窗体InitializeComponent(); 这个完成后 它才能得到一个不是NULL的对象.
5、应用:那么如何编写我的组件与UI框架无关呢?
看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用Control.Invoke, Control.BeginInvoke。这些都是很好的解决方案,不过有两个问题:
1. 必须的引用System.Windows.Forms,然后 using System.Windows.Forms
2. 代码结构比较零乱。(实际上这个也是#1 导致的)
微软提供了另外一个比较优雅的解决方案,就是 System.Threading. SynchronizationContext。 可以看出,它不在
namesapce System.Windows.Forms 里面,因此我们可以理直气壮地用在BusinessLaryer,
Controler,甚至 module 里面。
其实在UI线程中使用的并不是SynchronizationContext这个类,而是WindowsFormsSynchronizationContext这个东东,它是SynchronizationContext的派生类。
以下是SynchronizationContext在winform中实际应用:
案例一
View Code案例二
View Code
那么SynchronizationContext的Send()和Post()二个方法有什么区别呢?
Send() 是简单的在当前线程上去调用委托来实现(同步调用)。也就是在子线程上直接调用UI线程执行,等UI线程执行完成后子线程才继续执行。
Post() 是在线程池上去调用委托来实现(异步调用)。这是子线程会从线程池中找一个线程去调UI线程,子线程不等待UI线程的完成而直接执行自己下面的代码。
二、同步上下文的派生
同步上下文的实际"上下文"没有明确定义。不同的框架和主机可以*地定义自己的上下文。了解这些不同的实现及其局限性,可以准确地阐明同步上下文概念的作用和不足之处。我将简要讨论其中的一些实现。
1、WinForm 的同步上下文
WindowsFormsSynchronizationContext继承自SynchronizationContext
命名空间:System.Windows.Forms.dll:System.Windows.Forms
实现:
Windows Forms应用程序将创建WindowsFormsSynchronizationContext并将其安装为创建UI控件的任何线程的当前上下文。
此SynchronizationContext在UI控件上使用ISynchronizeInvoke方法,该控件将委托传递到基础Win32消息循环。
WindowsFormsSynchronizationContext的上下文是单个UI线程。
排队到WindowsFormsSynchronizationContext的所有委托一次执行一次;它们由特定的UI线程按排队顺序执行。当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。
2、WPF框架中同步上下文
DispatcherSynchronizationContext继承自SynchronizationContext
命名空间:WindowsBase.dll:System.Windows.Threading
实现:
Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。
WPF和Silverlight应用程序使用DispatcherSynchronizationContext,该代理将对UI线程的Dispatcher的委托以“Normal”优先级排队。
当线程通过调用Dispatcher.Run开始循环调度器 ,将这个初始化完成的 同步上下文 安装到当前上下文。
DispatcherSynchronizationContext的上下文是单个UI线程。
排队到DispatcherSynchronizationContext的所有委托均由特定的UI线程一次按其排队的顺序执行。当前实现为每个*窗口创建一个DispatcherSynchronizationContext,即使它们都共享相同的基础Dispatcher。
3、默认同步上下文
(默认)SynchronizationContext
命名空间:mscorlib.dll:System.Threading
调度线程池线程的同步上下文。
实现:
默认SynchronizationContext是默认构造的SynchronizationContext对象。根据惯例,如果线程的当前SynchronizationContext为null,则它隐式具有默认的SynchronizationContext。
默认的同步上下文将异步委托排队到ThreadPool,但直接在调用线程上执行其同步委托。
因此,默认同步上下文涵盖所有ThreadPool线程以及任何调用Send的线程。默认SynchronizationContext“借用”线程调用Send,将它们带入上下文,直到委托完成。从这个意义上讲,默认上下文可以包括进程中的任何线程。
除非代码由ASP.NET托管,否则默认的SynchronizationContext将应用于ThreadPool线程。除非子线程设置自己的SynchronizationContext,否则默认的SynchronizationContext也会隐式应用于显式子线程(Thread类的实例)。因此,UI应用程序通常具有两个同步上下文:覆盖UI线程的UI
SynchronizationContext和覆盖ThreadPool线程的默认SynchronizationContext。
三、ExecutionContext 概述
1、ExecutionContext 实际上只是其他上下文的容器。因此在.net framework中ExecutionContext 包含:同步上下文、安全上下文、调用上下文、模拟上下文、区域性通常会与执行上下文一起流动。
2、在.net core中, 不支持安全上下文和调用上下文、同步上下文和ExecutionContex一起流动。
1、2两条内容来源:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.executioncontext?view=net-6.0
参考文章:
https://www.cnblogs.com/BigBrotherStone/p/12316599.html
https://blog.csdn.net/starrycraft/article/details/113658608
https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext
https://blog.csdn.net/kalvin_y_liu/article/details/117787437?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase