为了账号安全,请及时绑定邮箱和手机立即绑定

ExecutionContext 不会从异步方法向上流动调用堆栈

ExecutionContext 不会从异步方法向上流动调用堆栈

C#
慕姐8265434 2022-11-22 16:01:43

考虑以下代码:


private static async Task Main(string[] args)

{

    await SetValueInAsyncMethod();

    PrintValue();


    await SetValueInNonAsyncMethod();

    PrintValue();

}


private static readonly AsyncLocal<int> asyncLocal = new AsyncLocal<int>();


private static void PrintValue([CallerMemberName] string callingMemberName = "")

{

    Console.WriteLine($"{callingMemberName}: {asyncLocal.Value}");

}


private static async Task SetValueInAsyncMethod()

{

    asyncLocal.Value = 1;

    PrintValue();


    await Task.CompletedTask;

}


private static Task SetValueInNonAsyncMethod()

{

    asyncLocal.Value = 2;

    PrintValue();


    return Task.CompletedTask;

}

如果您在 .NET 4.7.2 控制台应用程序中运行此代码,您将获得以下输出:


SetValueInAsyncMethod: 1

Main: 0

SetValueInNonAsyncMethod: 2

Main: 2

我确实理解输出的差异源于这样一个事实,即SetValueInAsyncMethod它不是真正的方法,而是一个执行的状态机,它在内部AsyncTaskMethodBuilder捕获并且只是一个常规方法。ExecutionContextSetValueInNonAsyncMethod

但即使有了这种理解,我仍然有一些问题:

  1. 这是错误/缺失的功能还是有意的设计决定?

  2. 在编写依赖于 的代码时,我是否需要担心这种行为AsyncLocal?比如说,我想编写我的TransactionScope-wannabe,它通过等待点来传输一些环境数据。AsyncLocal这里够了吗?

  3. 当归结为在整个“逻辑代码流”中保留值时,在 .NET 中是否还有其他替代方法AsyncLocalCallContext.LogicalGetData/ ?CallContext.LogicalSetData


查看完整描述

2 回答

?
慕尼黑5688855

TA贡献1579条经验 获得超2个赞

这是错误/缺失的功能还是有意的设计决定?

这是一个有意的设计决定。具体来说,async状态机为其逻辑上下文设置“写时复制”标志。

与此相关的是所有同步方法都属于它们最近的祖先async方法。

在编写依赖于 AsyncLocal 的代码时,我是否需要担心这种行为?比如说,我想编写我的 TransactionScope-wannabe,它通过等待点传输一些环境数据。这里 AsyncLocal 够用吗?

像这样的大多数系统都AsyncLocal<T>结合了IDisposable清除AsyncLocal<T>值的模式。组合这些模式可确保它适用于同步或异步代码。AsyncLocal<T>如果消费代码是一个async方法,它自己会工作得很好;与它一起使用IDisposable确保它可以同时使用async和同步方法。

当涉及到在整个“逻辑代码流”中保留值时,.NET 中的 AsyncLocal 和 CallContext.LogicalGetData / CallContext.LogicalSetData 是否还有其他替代方案?

不。


查看完整回答
反对 回复 2022-11-22
?
拉丁的传说

TA贡献1491条经验 获得超8个赞

这对我来说似乎是一个有意的决定。


正如您已经知道的那样,它SetValueInAsyncMethod被编译成一个隐式捕获当前 ExecutionContext 的状态机。当您更改AsyncLocal-variable 时,该更改不会“流”回调用函数。相反,SetValueInNonAsyncMethod它不是异步的,因此没有编译成状态机。因此,不会捕获 ExecutionContext 并且对AsyncLocal-variables 的任何更改对调用者都是可见的。


如果您出于任何原因需要它,您也可以自己捕获 ExecutionContext:


private static Task SetValueInNonAsyncMethodWithEC()

{

    var ec = ExecutionContext.Capture(); // Capture current context into ec

    ExecutionContext.Run(ec, _ => // Use ec to run the lambda

    {

        asyncLocal.Value = 3;

        PrintValue();

    });

    return Task.CompletedTask;

}

这将输出值 3,而 Main 将输出 2。


当然,简单地转换SetValueInNonAsyncMethod为异步让编译器为您做这件事要容易得多。


关于使用AsyncLocal(或就此而言CallContext.LogicalGetData)的代码,重要的是要知道更改调用的异步方法(或任何捕获的 ExecutionContext)中的值不会“回流”。但您当然仍然可以访问和修改AsyncLocal,只要您不重新分配它即可。


查看完整回答
反对 回复 2022-11-22
  • 2 回答
  • 0 关注
  • 9 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信