简体   繁体   English

内部异步使用块

[英]Async inside Using block

I have the following async function in C#: 我在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. 它允许执行任何异步函数execAsync ,它将SQL连接作为参数并通过提供连接对象并确保它将被正确关闭来使用它来进行数据库调用。

This function is then called from an action in a WebApi controller, as follows: 然后从WebApi控制器中的操作调用此函数,如下所示:

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. 这一切都很有效,直到我对WebApi操作进行了一次更改:我从中删除了异步/等待。 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 - ie if I navigate to the action's URL in the browser I do not get any errors. 这仍然似乎工作正常 - 即如果我在浏览器中导航到操作的URL我没有得到任何错误。 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. 在100次调用操作后,连接池达到其默认限制100,并且应用程序停止工作。

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? 我需要在CallDatabaseAsync()中进行哪些更改,以便绝对确保连接将被关闭,无论如何?

In ASP.NET, each request has a special SynchronizationContext . 在ASP.NET中,每个请求都有一个特殊的SynchronizationContext This synchronization context makes the code that runs after the await use the same "context" of the original request. 此同步上下文使得在await之后运行的代码使用原始请求的相同“上下文”。 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. 例如,如果await之后的代码访问当前的HttpContext ,它将访问属于同一ASP.NET请求的HttpContext

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. 现在,异步数据库访问完成时,它会尝试使用SynchronizationContext ,它捕获的之前await运行后的代码await (其中包括部署SQL连接的代码),但它无法找到它了,因为请求已经终止。

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. 在这种情况下你可以做的是使await之后的代码不依赖于当前的ASP.NET请求的SynchronizationContext ,而是在线程池线程上运行。 You can do this via the ConfigureAwait method like this: 您可以通过ConfigureAwait方法执行此操作,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM