Mister Epic
Mister Epic

Reputation: 16743

Persist thread's context across async calls

I am trying to disentangle my logs by grouping my log messages together. Ideally, I'd append to each message some identifier indicating that it is part of a group of commands achieving some piece of work.

If this were a single threaded application, the thread's ID would be a candidate. However, this solution makes heavy use of async Tasks, which precludes its use. Is it possible to get something like the following to work, where every execution of Main() would always print out the same thread ID?

static async void Main()
{
    var t = Task.Run(async () => await MyAsyncMethod());
    await t;
}

private static async Task MyAsyncMethod()
{
  Debug.WriteLine("Log message: {0}", Thread.CurrentThread.ManagedThreadId);

  await Task.Delay(1000);

  Debug.WriteLine("Log message: {0}", Thread.CurrentThread.ManagedThreadId);

  await Task.Delay(1000);

  Debug.WriteLine("Log message: {0}", Thread.CurrentThread.ManagedThreadId);
}

Upvotes: 1

Views: 904

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457177

The best approach IMO is what @SriramSakthivel suggested: just generate an id and pass it around. If you have a "message" type, you could just add it to that type.

However, if this would cause too much of a code change, then you can pass it implicitly. I have a blog post with the gory details; the general idea is that you store the data as part of the logical call context:

public static class MyContext
{
  private static readonly string name = Guid.NewGuid().ToString("N");

  private sealed class Wrapper : MarshalByRefObject
  {
    public Guid Value { get; set; }
  }

  public static Guid CurrentContext
  {
    get
    {
      var ret = CallContext.LogicalGetData(name) as Wrapper;
      return ret == null ? Guid.Empty : ret.Value;
    }

    set
    {
      CallContext.LogicalSetData(name, new Wrapper { Value = value });
    }
  }
}

Then you can use it from your code as such:

private static async Task MyAsyncMethod()
{
  MyContext.CurrentContext = Guid.NewGuid();

  Debug.WriteLine("Log message: {0}", MyContext.CurrentContext);

  await Task.Delay(1000);

  Debug.WriteLine("Log message: {0}", MyContext.CurrentContext);

  await Task.Delay(1000);

  Debug.WriteLine("Log message: {0}", MyContext.CurrentContext);
}

Note that this approach makes two crucial assumptions:

  1. Your code runs on .NET 4.5 or newer.
  2. The data being stored is immutable (which is true in this case, since Guid is immutable).

It looks like something like this will be builtin in .NET 4.6.

Upvotes: 4

Related Questions