在C#异步编程中,ActivitySource用来创建Activity以描述操作耗时与依赖关系,OpenTelemetry则负责将这些Activity导出为追踪数据。当代码中存在大量await、Task.Run或自定义任务调度时,Activity所依赖的上下文可能会断裂,造成链路不完整。理解两者在异步环境下的传递机制,是构建可靠可观测系统的前提。

Activity与异步上下文的基础关系
System.Diagnostics.Activity内部通过AsyncLocal存储当前活动实例。AsyncLocal会随着异步方法的await点自动流动到后续延续,因此大多数正常使用await的代码不需要额外处理。
常见断裂场景
- 使用Task.Run将工作推到线程池,且外部已丢失父上下文
- 在异步方法中错误使用async void,导致上下文无法传播
- 通过Parallel或自定义Thread直接执行,绕过AsyncLocal流动
基础用法示例
下面展示一个在异步方法中利用ActivitySource创建并自动传递上下文的例子。
using System.Diagnostics;
public class DemoService
{
private static readonly ActivitySource _source = new ActivitySource("MyApp.Demo");
public async Task DoWorkAsync()
{
// 创建Activity,当前上下文会自动进入AsyncLocal
using var activity = _source.StartActivity("DoWork");
activity?.SetTag("step", "begin");
// await点之后,Activity上下文仍可用
await Task.Delay(100);
await InnerAsync();
}
private async Task InnerAsync()
{
// 这里能取到父Activity,因为AsyncLocal随await流动
if (Activity.Current != null)
{
Activity.Current.SetTag("inner", "called");
}
await Task.Delay(50);
}
}
OpenTelemetry如何接入
在程序启动时注册OpenTelemetry的Tracing,并添加对ActivitySource的监听,导出的数据就会包含上述异步链路。
using OpenTelemetry;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracer =>
{
tracer.AddSource("MyApp.Demo")
.AddAspNetCoreInstrumentation()
.AddConsoleExporter();
});
Task.Run中的上下文处理
如果必须在Task.Run中执行,并且希望保留当前Activity,应在外部捕获后再传入,避免上下文丢失。
public void RunWithContext()
{
var parent = Activity.Current;
Task.Run(() =>
{
// 手动恢复上下文,再创建子Activity
using var scope = parent?.AddActivity?();
using var act = _source.StartActivity("TaskRunWork");
// 业务逻辑
});
}
注意async void
应避免在库代码中使用async void,它会导致Activity.Current在触发后无法回到调用方上下文,OpenTelemetry可能记录到孤立跨度。
小结对照表
| 场景 | 上下文是否连续 | 建议 |
|---|---|---|
| 普通await调用 | 是 | 直接StartActivity |
| Task.Run新任务 | 否(默认) | 显式传递parent |
| async void方法 | 易断裂 | 改为async Task |
结论
只要遵循await异步模型、避免async void、谨慎使用Task.Run,C#中的ActivitySource与OpenTelemetry就能在异步代码里稳定传递上下文,生成完整的分布式追踪链路。
C#ActivitySourceOpenTelemetry异步上下文分布式追踪修改时间:2026-07-25 04:30:24