AsyncLocal<Activity?>在无await的async方法中为何导致Activity父级丢失?
异步方法无await时Activity父级丢失问题解析
示例代码
public class Worker { private static readonly ActivitySource Source = new ActivitySource("Worker"); public static void DoWork() { var work = Source.StartActivity(); // <----------------------------------put a breakpoint here } /* public static async Task DoWork() { var work = Source.StartActivity(); // <----------------------------------put a breakpoint here } */ } internal class Program { static void Main(string[] args) { using var listener = new ActivityListener { ShouldListenTo = source => true, Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData }; ActivitySource.AddActivityListener(listener); Worker.DoWork(); Worker.DoWork(); } }
问题现象
- 当
Worker.DoWork()为静态无返回值方法时,第二次调用生成的Activity会以第一次的Activity为父级,符合预期。 - 将其改为
public static async Task DoWork()(未使用await关键字)后,第二次调用生成的Activity无父级,尽管方法是同步执行的。
原因分析
核心问题出在AsyncLocal<Activity?> s_current的上下文管理逻辑,以及async方法的编译特性:
普通同步方法的上下文行为
同步方法中,调用StartActivity()会将当前Activity赋值给s_current,方法执行完毕后不会主动重置这个值。因此第二次调用时,s_current保留着第一次的Activity实例,自然会作为新Activity的父级。async方法的状态机特性
哪怕async方法里没有await关键字,.NET编译器仍会为它生成异步状态机。这个状态机在执行时会做两个关键操作:- 进入方法时,保存当前的
AsyncLocal上下文快照; - 方法执行完毕后,自动恢复进入方法前的
AsyncLocal状态。
- 进入方法时,保存当前的
第一次调用async版本的DoWork()时,进入方法前s_current是null,状态机执行完后会把s_current重置回null。因此第二次调用时,s_current已经没有父Activity的引用,新生成的Activity就没有父级了。
内容的提问来源于stack exchange,提问作者user25521292
相关产品推荐
相关产品推荐

