最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

清除ExecutionContext,阻止 AsyncLocal 在异步流、Thread中传递

来源:博客园

前言:


(资料图片)

自从使用了AsyncLocal 后,就发现AsyncLocal 变量像个臭虫一样,在有AsyncLocal 变量的线程中启动的 Task 、或者 Thread 都会附带AsyncLocal 变量。

在项目使用AsyncLocal 实现了全局、局部 工作单元 ,但是就无法在后续作业中开启多个线程了(需求就是要开启多个线程,俺也没得办法),后续启动的多线程都会带有AsyncLocal 变量,直接导致报错,例如 DBContext 不是线程安全的错之类的....。

其实我一直认为在一个Http请求中开启多个线程,不合适,应该把需要执行的任务交给 “后台工作线程” ,或者交给 “后台Job” ,但现实世界中的情况就是很复杂,怎么办?就是要在Http请求中开启多个线程,还能怎么办呢,去解决ExecutionContext 、AsyncLocal 传递的问题吧。

人天天都学到一点东西,而往往所学到的是发现昨日学到的是错的。

Thread 中的ExecutionContext

创建一个线程,并启动,Thread执行的委托中会取到“AsyncLocalTest.Lang.Value” 在线程外部设置的值。

为啥Thread会取到外部的AsyncLocal 变量中的值呢?深入源代码看下,如下图。

好家伙,Thread.Start() 原来线程启动时,就去执行ExecutionContext.Capture()获取了线程执行上下文,即ExecutionContext

如下图,可以看到在Thread线程中可以获取到ExecutionContext ,从ExecutionContext中可以看到存储在上面的AsyncLocal 变量

Task中的ExecutionContext

声明Task时,深入源代码查看

Task 会再执行一个内部构造函数

Task 构造函数中,原来还是通过执行 ExecutionContext.Capture() 获取了ExecutionContext

创建一个Task时,Task就自动获取了“线程执行上下文 即 ExecutionContext”。

阻止ExecutionContext流动

如何阻止ExecutionContext流动,请查看这篇文章https://www.cnblogs.com/eventhorizon/p/12240767.html#3executioncontext-%E7%9A%84%E6%B5%81%E5%8A%A8 ,就不再赘述。

实现一个局部干净的ExecutionContext

1.实现一个DisposeAction ,不知道怎么称呼,请看代码吧,源代码来只ABP框架,我直接copy过来的。原理,就是Using代码块释放时,执行这个 “Action 委托”。

///     /// 源代码来自ABP Vnext框架    ///     public class DisposeAction : IDisposable    {        private readonly Action _action;        public DisposeAction([NotNull] Action action)        {            _action = action ?? throw new ArgumentNullException(nameof(action));        }        public void Dispose()        {            _action();        }    }
DisposeAction

2. 众所周知ExecutionContext.SuppressFlow() ,阻断 ExecutionContext 流动 。ExecutionContext.RestoreFlow(), 启动 ExecutionContext 流动 。

3. 实现局部阻断ExecutionContext 流动核心代码

public class SuppressExecutionContextFlow    {        public static IDisposable CleanEnvironment()        {            // 阻断 ExecutionContext 流动            ExecutionContext.SuppressFlow();            return new DisposeAction(() =>            {                if (ExecutionContext.IsFlowSuppressed())                {                    ExecutionContext.RestoreFlow();                }            });        }    }
SuppressExecutionContextFlow.CleanEnvironment

4.测试代码,随便调试下

//6.创建一个干净的 ExecutionContext 环境,供使用var scheduler = new QueuedTaskScheduler(2);AsyncLocalTest.Lang.Value = "test";using (SuppressExecutionContextFlow.CleanEnvironment()){    Task task11 = new Task(() =>    {        var aa = ExecutionContext.Capture();        Console.WriteLine("task11线程:" + AsyncLocalTest.Lang.Value);    });    Thread th = new Thread(() =>    {        var aa = ExecutionContext.Capture();        Console.WriteLine("th线程:" + AsyncLocalTest.Lang.Value);    });    th.Start();    task11.Start(scheduler);}Console.WriteLine("主线程:" + AsyncLocalTest.Lang.Value);Console.Read();
干净的 ExecutionContext 环境

调试.gif

自此实现一个局部干净的ExecutionContext 完成,我的代码参考https://github.com/qiqiqiyaya/Learning-Case/tree/main/CleanExecutionContext

关键词: