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