多线程程序在.net框架出现之前就已经存在了。这些程序通常需要一个线程将一个工作单元传递给另一个线程。Windows程序以消息循环为中心,因此许多程序员使用这个内置队列来传递工作单元。每个想要以这种方式使用Windows消息队列的多线程程序都必须定义自己的自定义Windows消息和处理它的约定。
当.net框架首次发布时,这种通用模式被标准化了。那时,. net支持的唯一GUI应用程序类型是Windows窗体。然而,框架设计者预期了其他模型,他们开发了一个通用的解决方案。ISynchronizeInvoke诞生了。而SynchronizationContext就是用来取代ISynchronizeInvoke。
同步上下文:这里同步是动词,据有归类的功能,假如有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线程。
注意:cpu每30毫秒切换一次
无论是什么平台(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线程能获取到 值,这是创建一个副本,不同ExecutionContent.capture,
SynchronizationContext maincontent= SynchronizationContext.Current;//这是创建一个副本。ExecutionContent.capture是捕获引用
//asp.net 和控制获取的结果是null
SynchronizationContext maincontent= SynchronizationContext.Current;
通过UI线程与工作线程的时序图可以看出整个更新的步骤:
整个过程中关键的是主线程的SynchronizationContext,SynchronizationContext在通讯中充当传输者的角色。在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。
传输者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的对象.
看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用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中实际应用:
案例一
案例二
同步上下文的实际"上下文"没有明确定义。不同的框架和主机可以自由地定义自己的上下文。了解这些不同的实现及其局限性,可以准确地阐明同步上下文概念的作用和不足之处。我将简要讨论其中的一些实现。
WindowsFormsSynchronizationContext继承自SynchronizationContext
命名空间:System.Windows.Forms.dll:System.Windows.Forms
实现:
Windows Forms应用程序将创建WindowsFormsSynchronizationContext并将其安装为创建UI控件的任何线程的当前上下文。
此SynchronizationContext在UI控件上使用ISynchronizeInvoke方法,该控件将委托传递到基础Win32消息循环。
WindowsFormsSynchronizationContext的上下文是单个UI线程(单线程的)。
排队到WindowsFormsSynchronizationContext的所有委托一次执行一次;它们由特定的UI线程按排队顺序执行。当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。
为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:
async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}
源代码
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using MS.Win32;
public sealed class DispatcherSynchronizationContext : SynchronizationContext
{
internal Dispatcher _dispatcher;
private DispatcherPriority \_priority;
public DispatcherSynchronizationContext()
: this(Dispatcher.CurrentDispatcher, DispatcherPriority.Normal)
{
}
public DispatcherSynchronizationContext(Dispatcher dispatcher)
: this(dispatcher, DispatcherPriority.Normal)
{
}
public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
}
Dispatcher.ValidatePriority(priority, "priority");
\_dispatcher = dispatcher;
\_priority = priority;
SetWaitNotificationRequired();
}
public override void Send(SendOrPostCallback d, object state)
{
if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && \_dispatcher.CheckAccess())
{
\_dispatcher.Invoke(DispatcherPriority.Send, d, state);
}
else
{
\_dispatcher.Invoke(\_priority, d, state);
}
}
public override void Post(SendOrPostCallback d, object state)
{
\_dispatcher.BeginInvoke(\_priority, d, state);
}
public override int Wait(IntPtr\[\] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (\_dispatcher.\_disableProcessingCount > 0)
{
return UnsafeNativeMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll, millisecondsTimeout, false);
}
return SynchronizationContext.WaitHelper(waitHandles, waitAll, millisecondsTimeout);
}
public override SynchronizationContext CreateCopy()
{
if (BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance())
{
return this;
}
if (BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority())
{
return new DispatcherSynchronizationContext(\_dispatcher, \_priority);
}
return new DispatcherSynchronizationContext(\_dispatcher, DispatcherPriority.Normal);
}
}
DispatcherSynchronizationContext继承自SynchronizationContext
命名空间:WindowsBase.dll:System.Windows.Threading
实现:
Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。
WPF和Silverlight应用程序使用DispatcherSynchronizationContext,该代理将对UI线程的Dispatcher的委托以“Normal”优先级排队。
当线程通过调用Dispatcher.Run开始循环调度器 ,将这个初始化完成的 同步上下文 安装到当前上下文。
DispatcherSynchronizationContext的上下文是单个UI线程(单线程的。
排队到DispatcherSynchronizationContext的所有委托均由特定的UI线程一次按其排队的顺序执行。当前实现为每个顶级窗口创建一个DispatcherSynchronizationContext,即使它们都共享相同的基础Dispatcher。
为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:
async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}
源代码
// System.Threading.SynchronizationContext
using System;
using System.Runtime.CompilerServices;
using System.Threading;
[NullableContext(1)]
[Nullable(0)]
public class SynchronizationContext
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
\[TupleElementNames(new string\[\] { "d", "state" })\]
public static Action<ValueTuple<SendOrPostCallback, object>> <>9\_\_8\_0;
internal void <Post>b\_\_8\_0(\[TupleElementNames(new string\[\] { "d", "state" })\] ValueTuple<SendOrPostCallback, object> s)
{
s.Item1(s.Item2);
}
}
private bool \_requireWaitNotification;
\[Nullable(2)\]
public static SynchronizationContext Current
{
\[NullableContext(2)\]
get
{
return Thread.CurrentThread.\_synchronizationContext;
}
}
private static int InvokeWaitMethodHelper(SynchronizationContext syncContext, IntPtr\[\] waitHandles, bool waitAll, int millisecondsTimeout)
{
return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
}
protected void SetWaitNotificationRequired()
{
\_requireWaitNotification = true;
}
public bool IsWaitNotificationRequired()
{
return \_requireWaitNotification;
}
public virtual void Send(SendOrPostCallback d, \[Nullable(2)\] object state)
{
d(state);
}
public virtual void Post(SendOrPostCallback d, \[Nullable(2)\] object state)
{
ThreadPool.QueueUserWorkItem(<>c.<>9\_\_8\_0 ?? (<>c.<>9\_\_8\_0 = new Action<ValueTuple<SendOrPostCallback, object>>(<>c.<>9.<Post>b\_\_8\_0)), new ValueTuple<SendOrPostCallback, object>(d, state), false);
}
public virtual void OperationStarted()
{
}
public virtual void OperationCompleted()
{
}
\[CLSCompliant(false)\]
public virtual int Wait(IntPtr\[\] waitHandles, bool waitAll, int millisecondsTimeout)
{
return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
}
\[CLSCompliant(false)\]
protected static int WaitHelper(IntPtr\[\] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (waitHandles == null)
{
throw new ArgumentNullException("waitHandles");
}
return WaitHandle.WaitMultipleIgnoringSyncContext(waitHandles, waitAll, millisecondsTimeout);
}
\[NullableContext(2)\]
public static void SetSynchronizationContext(SynchronizationContext syncContext)
{
Thread.CurrentThread.\_synchronizationContext = syncContext;
}
public virtual SynchronizationContext CreateCopy()
{
return new SynchronizationContext();
}
}
(默认)SynchronizationContext位于:mscorlib.dll:System.Threading
Default SynchronizationContext
是默认构造的 SynchronizationContext
对象。
Default SynchronizationContext
。Default SynchronizationContext
将其异步委托列队到 ThreadPool
,但在调用线程上直接执行其同步委托。Default SynchronizationContext
涵盖所有 ThreadPool
线程以及任何调用 Send
的线程。Send
的线程们,将这些线程放入这个上下文,直至委托执行完成Default SynchronizationContext
应用于 线程池 线程,除非代码由 ASP.NET 承载。Default SynchronizationContext
还隐式应用于显式子线程(Thread 类的实例),除非子线程设置自己的 SynchronizationContext
。因此,UI 应用程序通常有两个同步上下文:
UI SynchronizationContext
Default SynchronizationContext
默认SynchronizationContext是多线程的
默认的同步上下文只是将任务安排到线程池操作队列
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
public virtual void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
d(state);
}
public delegate void SendOrPostCallback(object state);
设置同步上下文
using System;
using System.Threading;
using System.Threading.Tasks;
namespace GetSetContext
{
internal static class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}");
SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());
Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}");
await Task.Delay(100);
Console.WriteLine("Completed!");
}
private class MySynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine($"Continuation dispatched to {nameof(MySynchronizationContext)}");
d.Invoke(state);
}
}
}
}
这段代码输出以下内容:
Current Synchronization Context: null
Current Synchronization Context: MySynchronizationContext
Continuation dispatched to MySynchronizationContext
Completed!
BackgroundWorker
运行流程
BackgroundWorker
捕获并使用调用 RunWorkerAsync
的线程的 同步上下文Default SynchronizationContext
中执行DoWork
RunWorkerCompleted
事件UI同步上下文 中只有一个 BackgroundWorker
,因此 RunWorkerCompleted
在 RunWorkerAsync
捕获的 UI同步上下文中执行(如下图)。
UI同步上下文中的嵌套 BackgroundWorker
嵌套: BackgroundWorker
从其 DoWork
处理程序启动另一个 BackgroundWorker
BackgroundWorker
不会捕获 UI同步上下文DoWork
由 线程池 线程使用 默认同步上下文 执行。
RunWorkerAsync
将捕获默认 SynchronizationContext
RunWorkerCompleted
默认情况下,控制台应用程序 和 Windows服务 中的所有线程都只有 Default SynchronizationContext
,这会导致一些基于事件的异步组件失败(也就是没有UI同步上下文的特性)
Nito.Async
库的 ActionThread
类可用作通用同步上下文实现。三、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
ExecutionContext 实际上是一个 state 包
ExecutionContext 是使用静态方法 Capture 捕获的:
// 周围环境的 state 捕获到 ec 中
ExecutionContext ec = ExecutionContext.Capture();
通过静态方法 Run ,在委托(Run方法的参数)调用时恢复 ExecutionContext
ExecutionContext.Run(ec, delegate
{
… // 这里的代码将上述 ec 的状态视为周围环境
}, null);
所有派生异步工作的方法都以这种方式捕获和还原 ExecutionContext 的。
例如:
Task.Run
时,对 Run
的调用将从调用线程中捕获 ExecutionContext ,并将该 ExecutionContext 实例存储到 Task
对象中Task.Run
的委托作为该 Task
执行的一部分被调用时,它是使用存储的 ExecutionContext 通过 ExecutionContext.Run
来完成的以下所有异步API的执行都是捕获 ExecutionContext 并将其存储,然后在调用某些代码时再使用存储的 ExecutionContext。
Task.Run
ThreadPool.QueueUserWorkItem
Delegate.BeginInvoke
Stream.BeginRead
DispatcherSynchronizationContext.Post
当我们谈论“flowing ExecutionContext”时,我们实际上是在讨论:
ExecutionContext类创建副本以便传播
前面我们介绍了 SynchronizationContext
是如何调度线程的,现在,我们要进行进行一次对比:
flowing ExecutionContext 在语义上与 capturing and posting to a SynchronizationContext 完全不同。
task.ConfigureAwait(bool) 传入false关闭为 **false** ,则**等待者(awaiter)** 不检查 **SynchronizationContext** ,就像没有一样
当 ExecutionContext 流动时,您是从一个线程捕获 state ,然后还原该 state
当您捕获并使用 SynchronizationContext 时,不会发生这种情况。
SynchronizationContext.Post
只是使用捕获的状态来调用委托,而不是在调用委托时设置该状态为当前状态参考文章:
手机扫一扫
移动阅读更方便
你可能感兴趣的文章