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