C# 当涉及异步调用时,如何跟踪工作单元?

C# 当涉及异步调用时,如何跟踪工作单元?,c#,multithreading,asynchronous,C#,Multithreading,Asynchronous,当前线程有一个线程ID,可以在thread.CurrentThread.ManagedThreadId访问该线程。线程内的所有同步调用显然都共享此ID 但是,如果调用异步方法,异步调用将显示不同的托管线程ID public void ThreadTest() { var currentThreadId = Thread.CurrentThread.ManagedThreadId; var asyncThreadId = GetThreadIdAsy

当前线程有一个线程ID,可以在
thread.CurrentThread.ManagedThreadId
访问该线程。线程内的所有同步调用显然都共享此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);
    }
public void ThreadTest()
{
var currentThreadId=Thread.CurrentThread.ManagedThreadId;
var asynchThreadId=GetThreadIdAsync().Result;
currentThreadId.ShouldEqual(asyncThreadId);//失败
}
私有异步任务GetThreadIdAsync()
{
返回wait Task.Run(()=>Thread.CurrentThread.ManagedThreadId);
}

我希望通过某种方式能够看到第二个方法与第一个方法是同一工作单元的一部分,如果不是通过
ManagedThreadId
,则是通过类似的方式。它们是否共享任何上下文?

.NET有一个
ExecutionContext
,它通过异步调用流动(事实上,它在使用几乎所有的线程API时流动,例如thread pool.APM、计时器-除了
不安全*
线程池方法或使用
ExecutionContext.SuppressFlow()挂起时除外)

您可以使用
CallContext
CorrelationManager
AsyncLocal
ExecutionContext
添加数据

请注意,向执行上下文添加数据将迫使运行时在每次上下文流动时克隆它,,这可能代价高昂

CallContext
调用上下文类似于与执行上下文一起流动的字典

CallContext.LogicalSetData("MyData", 1);
await Task.Run(() => Console.WriteLine(CallContext.LogicalGetData("MyData"))); // prints 1
主要用于跟踪的
CorrelationManager
,使用
CallContext
,并允许存储两个特定值,一个
ActivityId
(一个
Guid
)和一个
LogicalOperationStack
。例如,WCF在分派服务方法时自动设置活动ID,甚至可以跨机器传递ID

Trace.CorrelationManager.ActivityId = Guid.NewGuid();
await Task.Run(() => Console.WriteLine(Trace.CorrelationManager.ActivityId));
AsyncLocal
此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);
static local=new asynchlocal();//你可以在任何地方保存这个本地文件
var tasks=Enumerable.Range(0,10)。选择(i=>Task.Run(异步()=>
{
本地值=i;
wait Task.Run(()=>Console.WriteLine($“{i}={local.Value}”);
}));
等待任务。何时(任务);

我很好奇-你为什么需要这个?@EliArbel有一些全局上下文信息,我希望能够在高级别上设置这些信息,而无需使用额外参数重构数千个方法。查看ILSpy,看来
AsyncLocal
所做的一切都是调用
ExecutionContext.GetLocalValue
ExecutionContext.SetLocalValue
。@OhadSchneider无需使用ILSpy,转到@ScottChamberlain Old habits:)@OhadSchneider早期版本中没有这些方法。它们是专门为
AsyncLocal
创建的