跟踪c#/.NET任务流程

10
我正在尝试找到一种方法来跟踪异步任务的执行流程,以便于理解与任务相关的初始流程。主要是用于日志记录、调试和保留特定执行流程的堆栈信息。
例如:如果我有一个服务器,有多个来自不同IP的客户端并且服务器需要为每个客户端执行涉及多个异步操作的工作流程;使用async/await机制时,记录这样的流程是困难的。
我还没有想出一种方式来包装这些任务,使得在日志中记录每个任务的初始描述。例如,如果我为一个描述为“处理10.0.3.4客户端”的动作启动了一个新的任务流程,则希望能够将此描述添加到该流程中创建的任何任务的每个日志项中。
当仅使用线程时很容易,因为你有线程静态变量。但对于任务而言,这是不可能的...我甚至尝试创建自己的任务调度程序来包装使用它的任何任务(即使使用async/await方法),但遇到了瓶颈,因为任务调度程序基础有时会使用新线程(即使没有隐含请求使用新线程)- 方法TryExecuteTaskInline有时会在新线程中运行。
有什么想法或建议可以实现这个需求吗?

1
你能否展示一下你所尝试做的事情的代码? - Yuval Itzchakov
2个回答

14
您可以使用 Trace.CorrelationManager.ActivityId 存储逻辑操作ID,或更好地存储逻辑操作ID的 ImmutableStack。它存储在 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 范围来确保每个 Push 都有一个对应的 Pop
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,但是在 .Net 4.5 中,LogicalOperationStack 是否与异步不兼容



谢谢,这就是我寻找的解决方案 :) 我只想提醒一下,您可以将其他对象推送到此堆栈中,而不仅仅是Guids。 - ThaPhoeniX
2
没错,但是请确保只推送不可变类型。 - i3arnon
@i3arnon:我明白你的意思了。现在我理解整个问题了。 - drowa
是的,这几乎和Stephen Cleary在这里所做的一样:https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html。 - Ohad Schneider
在不涉及远程操作的情况下使用CallContext可能被视为设计缺陷。 - Szymon Knop
@Knopo 我认为这太老了,已经不再相关。它比 async-await 早了约 5 年。 - i3arnon

2
通过任务,你可以将这种信息存储在执行上下文中。以下是一个示例,展示如何使用逻辑调用上下文来实现。Trace.CorrelationManager也是基于执行上下文构建的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接