2016-05-21 4 views
6

У меня есть следующие функции асинхронной в 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

Вы уверены, что это соединение, что оно не закрывается? –

+0

@PauloMorgado Довольно уверен. Все вызовы базы данных проходят через ** CallDatabaseAsync() **, и это конкретное действие контроллера вызывает ** CallDatabaseAsync() ** только один раз. – Andrew

+0

Не могли бы вы показать пример async func, который вы передаете этому родовому исполнителю? –

ответ

4

В ASP.NET каждый запрос имеет специальный номер SynchronizationContext. Этот контекст синхронизации делает код, который запускается после того, как await использует тот же «контекст» исходного запроса. Например, если код после await обращается к текущему HttpContext, он получит доступ к HttpContext, принадлежащему к одному и тому же запросу ASP.NET.

Когда запрос прекращается, контекст синхронизации этого запроса умирает вместе с ним. Теперь, когда завершается доступ к асинхронной базе данных, он пытается использовать SynchronizationContext, который он захватил до await, чтобы запустить код после await (который включает в себя код, который использует SQL-соединение), но он больше не может его найти, поскольку запрос прекратился.

Что вы можете сделать в этом случае, это сделать код после ожидания не зависящим от текущего запроса 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); 
    } 
} 
+0

Спасибо Якуб. Один из моих коллег предложил мне то же самое и объяснил, что вы сказали в своем ответе. Я тестировал его, и он работает должным образом, соединения всегда закрываются. Отметьте это как правильный ответ. – Andrew

Смежные вопросы