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?
Calls to "async" methods should not be blocking. Making blocking calls to async methods transforms code that was intended to be asynchronous into a blocking operation. Doing so can cause deadlocks and unexpected blocking of context threads.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.
await blocks the code execution within the async function, of which it ( await statement ) is a part. There can be multiple await statements within a single async function. When using async await , make sure to use try catch for error handling.
The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With