This is something that has come up a couple of times now in my code, regarding the interaction of Task and IDisposable, and I figured if it tripped me up it might trip other people up too.

If you're trying to structure your code well, you should have lots of small, well-named methods that may only be two or three significant lines of code (SLoC), like this:

public static string GetConnectionString(string account)  
{
    var url = AccountConnectionStringUrl(account);
    using (var client = new HttpClient())
    {
        return client.GetString(url);
    }
}

Now we have proper IOCP Async for things like HTTP, we would write this method like this:

public static async Task<string> GetConnectionString(string account)  
{
    var url = AccountConnectionStringUrl(account);
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync(url);
    }
}

At first glance, the return await in that method looks superfluous, and there's a vague inkling in our head that we're incurring the overhead of all that mysterious, magical coroutine/state machine code (MMCSMC) that the C# compiler generates when we invoke the await spirits. Surely it would be simpler and quicker to write this:

public static Task<string> GetConnectionString(string account)  
{
    var url = AccountConnectionStringUrl(account);
    using (var client = new HttpClient())
    {
        return client.GetStringAsync(url);
    }
}

After all, HttpClient has already taken care of creating a Task<string> for us, and that's what our method is returning, so why not shortcut it?

The answer, of course, is that async is hard.

In the second version of that Async method, here's what happens:

  1. A disposable HttpClient object is created
  2. A Task is started to download some text
  3. The client is disposed
  4. The in-progress Task is returned to the caller.

You see the problem? We've got a task floating around which is still attached to our client, but we've closed down and cleaned up that object. If the task still needs the HttpClient to complete (hint: it does) then it's going to get an ObjectDisposedException.

By contrast, the first version of that Async method runs (effectively*) like this:

  1. A disposable HttpClient object is created
  2. A Task is started to download some text
  3. The compiler-generated coroutine yields execution until the Task completes
  4. The Task completes
  5. The coroutine continues execution
  6. The client is disposed
  7. The completed task is returned to the caller.

*That's simplified, obviously; what actually gets returned to the caller is a Task<string> that wraps around all that coroutine complexity, but the effect is the same.

So, when you're working with IDisposable objects like HttpClient or SqlConnection, don't try and shortcut your Async methods, because you'll get bizarre Heisenbugs that defy debugging (it turns out those tasks sneakily complete while you're stepping through your broken code).

As for the overhead of the MMCSMC: yes, it is measurable (if only at the milliseconds level). But we use async/await for two reasons: to keep a GUI responsive while a long-running task (such as an ADO.NET query) executes; or to improve the scalability of our web applications, but not the raw performance. If either of those use-cases apply, then you need to be using async methods, and if you're using them you should probably just await them.

Note: As mentioned in the comments below, wrapping HttpClient instances in using blocks is not necessarily best practice. HttpClient is mostly thread-safe, which means you can reuse instances of it (with certain provisos). More information on that here.