Search code examples
c#async-awaitconfigureawait

Why HttpContext.Current is not null in async/await with ConfigureAwait


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

Solution

  • 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.