1

In a synchronous environment, it is easy to create a scoped context which allows you to attach out-of-band context to your current thread. Examples of this are the current TransactionScope or thread-static logging context.

using (new MyContext(5))
    Assert.Equal(5, MyContext.Current);

Assert.Equal(null, MyContext.Current);

It is easy to implement the context using a combination of IDisposable and a thread-static field.

Obviously, this falls apart when using async methods, because the context is based on a thread-static field. So, this fails:

using (new MyContext(5))
{
    Assert.Equal(5, MyContext.Current);

    await DoSomethingAsync();

    Assert.Equal(5, MyContext.Current);
}

And of course we would also want that the context is passed to the async methods in the call chain, so this should work as well:

using (new MyContext(5))
{
    Assert.Equal(5, MyContext.Current);

    await AssertContextIs(5);
}

Does anyone have an idea how this could be implemented? Losing out-of-band context when using the async/await pattern makes some pieces of the code really ugly.

Think of async WebAPI calls where you would like to use request-based context for logging purposes. You want your logger deep in the call stack to be aware of the request ID without having the need to pass that request ID all the way through the call stack using parameters.

Thanks for any help!

Jochen
  • 95
  • 9
  • Im not really understanding what doesn't work in your second example. You are still in the context of your `using` statement after you `await DoSomethingAsync`. – Yuval Itzchakov Sep 24 '14 at 11:02

2 Answers2

5

The best solution is to use HttpContext.Current.Items in ASP.NET, or IOwinRequest.[Get|Set] in OWIN. Yes, this does mean passing the request object everywhere, which makes sense if you think of your "context value" as belonging to that request. If you're uncomfortable with a "library" method knowing about OWIN, then it's easy to write a "context wrapper" object and pass that around instead.

However, if you are sure you don't want to pass anything around, then you can use LogicalCallContext with immutable data as I describe on my blog. If you're just concerned about logging, then you may find my AsyncDiagnostics library useful - it uses PostSharp to inject method names, and you can add your own info into the "async stack" as well.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

If you are in an hosted environment (you are talking about WebAPI) you should use the Request context (HttpContext.Current), not the thread as you don't manage threads and don't know when/if the original thread will be available neither how to get back on it.

The request context is properly handled using the await keyword.

Guillaume
  • 12,824
  • 3
  • 40
  • 48
  • First of all, HttpContext.Current is not guaranteed to be available (only for IIS hosted apps). So, that's a System.Web dependency which I definitely don't want to rely on. But, more important, my question is more general, the request ID was just an example of one use case. I'm definitely looking for a general solution to the problem: convey OOB context when using the async/await pattern. – Jochen Sep 24 '14 at 10:57
  • async/await relies on SynchronizationContext not Threads. So the solution is attaching your data to the context so you'll have a dependency on your context. If you want to avoid it, then avoid using context and provides the values as argument. – Guillaume Sep 24 '14 at 14:17