NET-SynchronizationContext
前言
最近在看CAP的源码,经常能看到ConfigureAwait(false)
,例如下面这一段:
public async Task PublishAsync(string stream, NameValueEntry[] message)
{
await ConnectAsync()
.ConfigureAwait(false);
await _redis!.GetDatabase().StreamAddAsync(stream, message)
.ConfigureAwait(false);
}
不明白这一句话的意义,于是乎查了一下资料,这里记录总结一下
SynchronizationContext是什么
先看一下MSDN上官方的解释:
SynchronizationContext是一个基类,它提供了没有同步的线程自由的上下文。实现了这个类的同步模型的类允许公共语言运行时内部的异步/同步操作能够在合适的同步模型上允许。该模型还简化了托管应用程序必须遵循的一些需求,以便在不同的同步环境下正确工作。同步模型的提供者可以扩展这个类,并为这些方法提供他们自己的实现。
链接:https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext?view=net-7.0
听起来非常的官方,但是上面这句话的大体意思是想说不同的框架在线程之间的通信方式不同,我们可能想要在正确的上下文中调用特定的代码,比如WPF中的Dispatcher.BeginInvoke
允许我们从另一个线程调用UI线程来执行具体的代码,SynchronizationContext
类就是这些实现的一个抽象类,它提供了一些方法,让我们可以在不同的上下文中执行代码。
SynchronizationContext公开了几个方法,我们可以通过这些方法来实现不同的上下文之间的通信,其中比较重要的一个方法就是Post
,它的定义如下:
public virtual void Post(SendOrPostCallback d, object? state);
这个方法的作用是将一个委托放入队列中,然后在合适的时候执行,默认的实现是通过ThreadPool.QueueUserWorkItem
来实现的,也就是说,这个方法的默认实现是将委托放入线程池中,然后在合适的时候执行。但是,我们可以通过继承SynchronizationContext
类来实现自己的同步上下文,然后重写Post
方法来实现不同的同步上下文,比如WPF中的DispatcherSynchronizationContext
就是通过重写Post
方法来实现的,调用方式就是通过调用Dispatcher.BeginInvoke
来实现。
从名字上来看,SynchronizationContext是我们当前代码运行的一个上下文环境,也就是说在异步程序中,当我们把一段业务代码委托给另一个线程执行时,我们捕获了当前的上下文环境,放到SynchronizationContext中,然后把它放到了Task
对象上,重点是我们可以捕获当前环境并传递给另一个线程,这样我们可以在另一个线程中恢复当前的上下文环境,然后执行我们的代码。
为什么需要SynchronizationContext?
有时候我们需要在另一个线程中执行一段代码,比如我们需要在另一个线程中更新UI,这时候我们就需要在另一个线程中恢复当前的上下文环境,然后执行我们的代码,这就是SynchronizationContext的作用。
具体原因这里不细说,了解WPF、Winform应该都知道UI控件的属性只能在UI线程更新
不是所有的框架都有SynchronizationContext
不是所有的框架都有SynchronizationContext,比如Asp.Net Core就没有,参考 https://stackoverflow.com/questions/18097471/what-does-synchronizationcontext-do
所以有些时候我们会发现在很多开源框架内都会使用ConfigureAwait(false)
来禁用SynchronizationContext,这样做的目的就是为了避免在不同的上下文中执行代码,比如在Asp.Net Core中,我们不需要在另一个线程中恢复当前的上下文环境,因为Asp.Net Core没有SynchronizationContext,所以我们可以使用ConfigureAwait(false)
来禁用SynchronizationContext,这样做的好处是可以提高性能,因为不需要在不同的上下文中执行代码。
同时需要注意的是,async await默认会捕获当前的运行上下文,如果上下文为空的话,则默认会在
TaskScheduler.Default
上执行,也就是在线程池内的任意一个线程执行,当然这个线程也有可能是UI线程或执行它的线程
每个线程都有一个SynchronizationContext
每个线程都有一个SynchronizationContext,这意味着如果我们将工作从一个线程池委托给另一个线程,我们可以获得当前运行环境的快照并将其传递给另一个线程