Async inside Using block

Andrew picture Andrew · May 21, 2016 · Viewed 7k times · Source

I have the following async function in C#:

private async Task<T> CallDatabaseAsync<T>(Func<SqlConnection, Task<T>> execAsync)
{
    using (var connection = new SqlConnection(_connectionString))
    {
        connection.Open();
        return await execAsync(connection);
    }
}

It allows to execute any async function execAsync that takes SQL connection as an argument and uses it to make a database call, by providing the connection object and ensuring it would be properly closed.

This function is then called from an action in a WebApi controller, as follows:

public async Task<HttpResponseMessage> MyAction()
{
    Func<SqlConnection, Task<SomeType>> execAsync = (function definition here);
    await CallDatabaseAsync(execAsync);
    return Request.CreateResponse(HttpStatusCode.OK);
}

This all works great until I make one change to the WebApi action: I remove async/await from it. I do not want to wait for the database call because I do not care about the result, I just want to fire and forget.

This still seems to work fine - i.e. if I navigate to the action's URL in the browser I do not get any errors. But actually there is a problem - the database connection does not get closed. After 100 calls to the action, connection pool reaches its default limit of a 100, and the application stops working.

What am I doing wrong? What do I need to change in CallDatabaseAsync() so that it absolutely ensures that the connection would be closed, no matter what?

Answer

Yacoub Massad picture Yacoub Massad · May 23, 2016

In ASP.NET, each request has a special SynchronizationContext. This synchronization context makes the code that runs after the await use the same "context" of the original request. For example, if the code after the await accesses the current HttpContext, it will access the HttpContext that belongs to the same ASP.NET request.

When a request terminates, the synchronization context of that request dies with it. Now, when the asynchronous database access completes, it tries to use the SynchronizationContext that it captured before the await to run the code after the await (which includes the code that disposes of the SQL connection), but it cannot find it anymore because the request has terminated.

What you can do in this case is make the code after the await not depend on the current ASP.NET request's SynchronizationContext, but instead run on a Thread-pool thread. You can do this via the ConfigureAwait method like this:

private async Task<T> CallDatabaseAsync<T>(Func<SqlConnection, Task<T>> execAsync)
{
    using (var connection = new SqlConnection(_connectionString))
    {
        connection.Open();
        return await execAsync(connection).ConfigureAwait(false);
    }
}