我正试图找到一种方法来跟踪异步任务执行流程,以便在任务中很容易理解,启动它的原始流程是什么。我需要它主要用于记录,调试和保留特定执行流的一种堆栈跟踪。
例如:如果我的服务器有多个来自多个IP的客户端,并且服务器需要为涉及许多异步操作的每个客户端执行工作流,那么每个执行流程涉及许多不同的任务;记录这样的流程很困难,特别是在使用async/await
机制时。
我还没有找到一种方法来包装任务,对于每个执行的任务,我会在记录时知道初始流程描述。例如,如果我为具有描述的操作启动新的任务流程 - “照顾10.0.3.4客户端”,我希望能够在从该流程创建的任何任务中为此流程中的每个日志项添加此描述
当仅使用线程时,它很容易,因为你有线程静态变量。任务不可能......
我甚至尝试创建自己的任务调度程序,它将包装任何使用它的任务(即使使用async/await
方法)但由于任务调度程序库有时会使用新线程(尽管没有'任何使用新线程的隐式请求) - 方法TryExecuteTaskInline
有时可以在新线程中运行。
我对如何实现这一点有任何想法或建议?
答案 0 :(得分:13)
您可以使用Trace.CorrelationManager.ActivityId
存储逻辑操作ID,甚至可以更好地存储ImmutableStack
逻辑操作ID。它存储在CallContext
中,并通过async
方法调用复制:
public static class LogicalFlow
{
private static readonly string _name = typeof (LogicalFlow).Name;
private static ImmutableStack<Guid> LogicalStack
{
get
{
return CallContext.LogicalGetData(_name) as ImmutableStack<Guid> ?? ImmutableStack.Create<Guid>();
}
set
{
CallContext.LogicalSetData(_name, value);
}
}
public static Guid CurrentId
{
get
{
var logicalStack = LogicalStack;
return logicalStack.IsEmpty ? Guid.Empty : logicalStack.Peek();
}
}
}
您可以将其用作IDisposable
,这样您就可以利用using
范围来确保每Pop
个Push
:
private static readonly Popper _popper = new Popper();
public static IDisposable StartScope()
{
LogicalStack = LogicalStack.Push(Guid.NewGuid());
return _popper;
}
private sealed class Popper : IDisposable
{
public void Dispose()
{
LogicalStack = LogicalStack.Pop();
}
}
用法:
using (LogicalFlow.StartScope())
{
Console.WriteLine(LogicalFlow.CurrentId);
await DoSomethingAsync();
Console.WriteLine(LogicalFlow.CurrentId);
}
此答案以前依赖于Trace.CorrelationManager.LogicalOperationStack
但Is LogicalOperationStack
incompatible with async in .Net 4.5
答案 1 :(得分:2)
使用任务,您可以将此类信息存储在execution context中。 Here's an example如何使用逻辑调用上下文来完成它,这是一种执行上下文。 Trace.CorrelationManager
也建立在执行上下文之上。