如何在涉及异步调用时跟踪工作单元?

时间:2016-07-07 10:17:19

标签: c# multithreading asynchronous

当前线程有一个可以在Thread.CurrentThread.ManagedThreadId访问的线程ID。线程内的所有同步调用显然都共享此ID。

但是如果调用异步方法,则异步调用会显示不同的托管线程ID。

    public void ThreadTest()
    {
        var currentThreadId = Thread.CurrentThread.ManagedThreadId;
        var asyncThreadId = GetThreadIdAsync().Result;
        currentThreadId.ShouldEqual(asyncThreadId); // fails
    }

    private async Task<int> GetThreadIdAsync()
    {
        return await Task.Run(() => Thread.CurrentThread.ManagedThreadId);
    }

我想某种方式能够看到第二种方法与第一种方法属于同一工作单元,如果不是ManagedThreadId,那么就是类似的方法。他们有共同的背景吗?

1 个答案:

答案 0 :(得分:3)

.NET有ExecutionContext流经异步调用(实际上它在使用几乎所有线程API时流动,例如线程池.APM,定时器 - 除了Unsafe*线程池方法或在使用ExecutionContext.SuppressFlow())暂停时。

您可以使用CallContextCorrelationManagerAsyncLocal<T>将数据添加到ExecutionContext

请注意,向执行上下文添加数据会强制运行时在每次流动时克隆上下文,可能代价高昂

CallContext

调用上下文就像一个与执行上下文一起流动的字典。

CallContext.LogicalSetData("MyData", 1);
await Task.Run(() => Console.WriteLine(CallContext.LogicalGetData("MyData"))); // prints 1

CorrelationManager,主要用于跟踪,使用CallContext并允许存储两个特定值,ActivityId(a Guid)和LogicalOperationStack 。例如,WCF在分派服务方法时自动设置活动ID,甚至可以跨机器传输ID。

Trace.CorrelationManager.ActivityId = Guid.NewGuid();
await Task.Run(() => Console.WriteLine(Trace.CorrelationManager.ActivityId));

AsyncLocal<T>

此API是在.NET 4.6中引入的,与CallContext相比有几个优点:

  • 支持更改通知
  • 已输入
  • 它不是静态的

示例:

static local = new AsyncLocal<int>(); // you can save this local anywhere

var tasks = Enumerable.Range(0, 10).Select(i => Task.Run(async () =>
{
    local.Value = i;
    await Task.Run(() => Console.WriteLine($"{i} = {local.Value}"));
}));
await Task.WhenAll(tasks);