ThaPhoeniX
ThaPhoeniX

Reputation: 113

Tracking c#/.NET tasks flow

I'm trying to find a way to keep a track on async tasks execution flow in a way that it would be easy to understand regarding task, what was the original flow that initiated it. I need it mostly for logging, debugging and preserving a sort of stack trace for a specific execution flow.

For example: If I have a server with many clients from multiple IPs and the server needs to execute a work flow for each client that involves many async actions thus involves many different tasks per execution flow; logging such a flow is difficult especially when using async/await mechanism.

I haven't figured out a way to wrap the tasks in a way that for each task that is executed I would know the initial flow description when logging. For example if I start a new flow of tasks for an action with description - "Taking care of 10.0.3.4 client" I want to be able to add this description for each log item coming out from this flow in any task that created from it.

When using threads only, its easy because you have thread static variables. with tasks its impossible... I even tried to create my own task scheduler that will wrap out any task that uses it (even when using async/await methods) but got into a dead end because the task scheduler base sometimes uses new threads (even though there wasn't any implicit request to use a new thread)- the method TryExecuteTaskInline can sometimes run in a new thread.

Any idea or suggestion of how can I achieve that?

Upvotes: 10

Views: 2267

Answers (2)

i3arnon
i3arnon

Reputation: 116558

You can use Trace.CorrelationManager.ActivityId to store a logical operation id, or even better store an ImmutableStack of logical operation ids. It is stored in the CallContext and is copied through the async method calls:

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();
        }
    }
}

You can use it as an IDisposable so you can utilize a using scope to make sure there's a Pop to every 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();
    }
}

Usage:

using (LogicalFlow.StartScope())
{
    Console.WriteLine(LogicalFlow.CurrentId);
    await DoSomethingAsync();
    Console.WriteLine(LogicalFlow.CurrentId);
}

This answer previously relied on Trace.CorrelationManager.LogicalOperationStack but Is LogicalOperationStack incompatible with async in .Net 4.5

Upvotes: 14

Anton Tykhyy
Anton Tykhyy

Reputation: 20076

With tasks, you can store this kind of information in the execution context. Here's an example how to do it using logical call context, which is one kind of execution context. Trace.CorrelationManager is built on top of the execution context, too.

Upvotes: 2

Related Questions