Reputation: 113
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
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
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