翼度科技»论坛 编程开发 .net 查看内容

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

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
前言:
  自从使用了 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 委托”。
  1.     /// <summary>
  2.     /// 源代码来自ABP Vnext框架
  3.     /// </summary>
  4.     public class DisposeAction : IDisposable
  5.     {
  6.         private readonly Action _action;
  7.         public DisposeAction([NotNull] Action action)
  8.         {
  9.             _action = action ?? throw new ArgumentNullException(nameof(action));
  10.         }
  11.         public void Dispose()
  12.         {
  13.             _action();
  14.         }
  15.     }
复制代码
DisposeAction    2. 众所周知 ExecutionContext.SuppressFlow() , 阻断 ExecutionContext 流动 。ExecutionContext.RestoreFlow(), 启动 ExecutionContext 流动 。 
    3. 实现局部阻断 ExecutionContext  流动核心代码
  1.     public class SuppressExecutionContextFlow
  2.     {
  3.         public static IDisposable CleanEnvironment()
  4.         {
  5.             // 阻断 ExecutionContext 流动
  6.             ExecutionContext.SuppressFlow();
  7.             return new DisposeAction(() =>
  8.             {
  9.                 if (ExecutionContext.IsFlowSuppressed())
  10.                 {
  11.                     ExecutionContext.RestoreFlow();
  12.                 }
  13.             });
  14.         }
  15.     }
复制代码
SuppressExecutionContextFlow.CleanEnvironment    4.测试代码,随便调试下
  1. //6.创建一个干净的 ExecutionContext 环境,供使用
  2. var scheduler = new QueuedTaskScheduler(2);
  3. AsyncLocalTest.Lang.Value = "test";
  4. using (SuppressExecutionContextFlow.CleanEnvironment())
  5. {
  6.     Task task11 = new Task(() =>
  7.     {
  8.         var aa = ExecutionContext.Capture();
  9.         Console.WriteLine("task11线程:" + AsyncLocalTest.Lang.Value);
  10.     });
  11.     Thread th = new Thread(() =>
  12.     {
  13.         var aa = ExecutionContext.Capture();
  14.         Console.WriteLine("th线程:" + AsyncLocalTest.Lang.Value);
  15.     });
  16.     th.Start();
  17.     task11.Start(scheduler);
  18. }
  19. Console.WriteLine("主线程:" + AsyncLocalTest.Lang.Value);
  20. Console.Read();
复制代码
干净的 ExecutionContext 环境    调试.gif
    

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

来源:https://www.cnblogs.com/youlicc/archive/2023/05/20/17410530.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具