内部异步使用块

时间:2016-05-21 00:53:36

标签: c# async-await using

我在C#中有以下异步功能:

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

它允许执行任何异步函数 execAsync ,它将SQL连接作为参数并使用它来进行数据库调用,方法是提供连接对象并确保它正确关闭。

然后从WebApi控制器中的操作调用此函数,如下所示:

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

这一切都很有效,直到我对WebApi操作进行了一次更改:我从中删除了异步/等待。我不想等待数据库调用,因为我不关心结果,我只是想解雇并忘记。

这仍然可以正常工作 - 例如,如果我在浏览器中导航到操作的URL,我不会收到任何错误。但实际上有一个问题 - 数据库连接没有关闭。在100次调用操作后,连接池达到其默认限制100,并且应用程序停止工作。

我做错了什么?我需要在 CallDatabaseAsync()中进行哪些更改,以便绝对确保连接将被关闭,无论如何?

1 个答案:

答案 0 :(得分:10)

在ASP.NET中,每个请求都有一个特殊的SynchronizationContext。此同步上下文使得在await之后运行的代码使用原始请求的相同“上下文”。例如,如果await之后的代码访问当前HttpContext,它将访问属于同一ASP.NET请求的HttpContext

当请求终止时,该请求的同步上下文将随之消失。现在,当异步数据库访问完成时,它会尝试使用SynchronizationContext之前捕获的await来运行await之后的代码(其中包含处理SQL的代码)连接),但由于请求已经终止,它无法再找到它。

在这种情况下你可以做的是在await之后使代码不依赖于当前的ASP.NET请求的SynchronizationContext,而是在线程池线程上运行。您可以通过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);
    }
}