The asychronous pattern in C# is great for simplifying the boilerplate of dealing with threads. It makes things look simple, but as with all abstractions, there are leaks .
In most cases, when an async method calls another and there’s no chain (e.g. public method calling a private method directly) return Task is fine, and avoids the overhead of the await state machine for that method.
BUT never do inside a using, or other scoped construct where the object required by the inner call requires context to successfully complete.
In the following example, the first variation, without the await, returns a database call that escapes from the context of the database connection at the end of the method, so when the call is made, the database connection has been closed. You need the await in the second example to postpone the disposal of the database connection until the GetUserAsync call returns.
In this example it’s fairly obvious, but the same pattern can cause threads to unravel far from where this fray was introduced.
class UserDetailsRepository
{
public UserDetailsRepository(IDbConnectionFactory dbConnectionFactory)
{
_dbConnectionFactory = dbConnectionFactory;
}
public Task<UserDetailsResult> GetUserDetails(string userId, CancellationToken cancellationToken = default)
{
using (var dbConnection = _dbConnectionFactory.CreateConnection())
{
return GetUserAsync(userId, dbConnection, cancellationToken);
}
}
public async Task<UserDetailsResult> GetUserDetailsAsync(string userId, CancellationToken cancellationToken = default)
{
using (var dbConnection = _dbConnectionFactory.CreateConnection())
{
return await GetUserAsync(userId, dbConnection, cancellationToken);
}
}
}
Top comments (0)