I have a library async function called from controller. I expected HttpContext.Current to be null after await with ConfigureAwait(false) everywhere, but in controller it is not null. Can somebody explain why?
//in libraby
public class MyClass
{
public async Task WaitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
var httpContext = System.Web.HttpContext.Current; // null, OK
}
}
public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
var class1 = new MyClass();
await class1.WaitAsync();
var httpContext = System.Web.HttpContext.Current; // not null, WHY???
return View("Index");
}
}
Though it is much more complex than that, you can picture await
as a kind of ContinueWith
. So if you write for instance:
DoSomeStuff();
await WaitAsync()
DoMoreStuff();
It is rewritten to:
DoSomeStuff();
WaitAsync().ContinueWith(_ => DoMoreStuff());
.ConfigureAwait
sets the context in which the continuation will execute. With ConfigureAwait(true)
(the default), the continuation will execute in the same context as the caller. With ConfigureAwait(false)
, the continuation will execute in the default invariant context, on the threadpool.
With our previous simplification, let's imagine ConfigureAwait(true)
will be rewritten to ContinueWithSameContext
and ConfigureAwait(false)
to ContinueWithThreadPool
.
Now what happens if we have nested methods? For instance, your code:
public async Task WaitAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
var httpContext = System.Web.HttpContext.Current; // null, OK
}
public async Task<ActionResult> Index()
{
var class1 = new MyClass();
await class1.WaitAsync();
var httpContext = System.Web.HttpContext.Current; // not null, WHY???
return View("Index");
}
This is rewritten too:
public Task WaitAsync()
{
return Task.Delay(TimeSpan.FromSeconds(1))
.ContinueWithThreadPool(_ =>
{
var httpContext = System.Web.HttpContext.Current; // null, OK
});
}
public Task<ActionResult> Index()
{
var class1 = new MyClass();
return class1.WaitAsync().ContinueWithSameContext(_ =>
{
var httpContext = System.Web.HttpContext.Current; // not null, WHY???
return View("Index");
}
}
Rewritten this way, you see that the continuation of WaitAsync
will run on the same context as Task<ActionResult> Index()
, explaining why the HttpContext isn't null.