跟踪c# /.. NET任务流

本文关键字:任务 NET 跟踪 | 更新日期: 2023-09-27 18:10:36

我正试图找到一种方法来跟踪异步任务执行流,以一种容易理解的方式,关于任务,启动它的原始流是什么。我主要需要它来记录、调试和保存特定执行流的堆栈跟踪。

例如:如果我有一个服务器,有来自多个ip的许多客户端,服务器需要为每个客户端执行一个涉及许多异步操作的工作流,因此每个执行流涉及许多不同的任务;记录这样的流是困难的,特别是当使用async/await机制时。

我还没有想出一种方法来包装任务,以一种方式,对于每个任务,我将知道日志记录时的初始流描述。例如,如果我为一个带有描述的操作启动一个新的任务流-"照顾10.0.3.4客户端",我希望能够为从该流程中创建的任何任务中产生的每个日志项添加此描述。

当只使用线程时,这很容易,因为你有线程静态变量。任务是不可能的……我甚至尝试创建我自己的任务调度器,它将包装任何使用它的任务(即使使用async/await方法),但由于任务调度器基础有时使用新线程(即使没有任何隐式请求使用新线程),因此进入了死胡同-方法TryExecuteTaskInline有时可以在新线程中运行。

有什么想法或建议,我怎么才能做到这一点?

跟踪c# /.. NET任务流

您可以使用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,但是LogicalOperationStack与。net 4.5中的async不兼容

对于任务,您可以将此类信息存储在执行上下文中。下面是一个如何使用逻辑调用上下文(一种执行上下文)来实现它的示例。Trace.CorrelationManager也是建立在执行上下文之上的。