A programação assíncrona "cresce" através da base de código. Foi comparado a um vírus zumbi . A melhor solução é permitir que ela cresça, mas às vezes isso não é possível.
Escrevi alguns tipos na minha biblioteca Nito.AsyncEx para lidar com uma base de código parcialmente assíncrona. Porém, não há solução que funcione em todas as situações.
Solução A
Se você possui um método assíncrono simples que não precisa ser sincronizado de volta ao seu contexto, pode usar Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Você não deseja usar Task.Wait
ou Task.Result
porque eles envolvem exceções AggregateException
.
Esta solução é apropriada apenas se MyAsyncMethod
não for sincronizada de volta ao seu contexto. Em outras palavras, todo await
em MyAsyncMethod
deve terminar com ConfigureAwait(false)
. Isso significa que não é possível atualizar nenhum elemento da interface do usuário nem acessar o contexto de solicitação do ASP.NET.
Solução B
Se MyAsyncMethod
precisar sincronizar de volta ao seu contexto, você poderá usar AsyncContext.RunTask
para fornecer um contexto aninhado:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Atualização 14/04/2014: nas versões mais recentes da biblioteca, a API é a seguinte:
var result = AsyncContext.Run(MyAsyncMethod);
(Não há problema em usar Task.Result
neste exemplo, porque RunTask
propagará Task
exceções).
O motivo pelo qual você pode precisar, em AsyncContext.RunTask
vez de, Task.WaitAndUnwrapException
é por causa de uma possibilidade bastante sutil de conflito que ocorre no WinForms / WPF / SL / ASP.NET:
- Um método síncrono chama um método assíncrono, obtendo a
Task
.
- O método síncrono faz uma espera de bloqueio no
Task
.
- O
async
método usa await
sem ConfigureAwait
.
- O
Task
não pode ser concluído nessa situação porque ele é concluído apenas quando o async
método é concluído; o async
método não pode ser concluído porque está tentando agendar sua continuação para SynchronizationContext
, e WinForms / WPF / SL / ASP.NET não permitirá que a continuação seja executada porque o método síncrono já está sendo executado nesse contexto.
Essa é uma das razões pelas quais é uma boa ideia usar o máximo possível ConfigureAwait(false)
em todos os async
métodos.
Solução C
AsyncContext.RunTask
não funcionará em todos os cenários. Por exemplo, se o async
método aguardar algo que exija a conclusão de um evento da interface do usuário, você entrará em conflito mesmo com o contexto aninhado. Nesse caso, você pode iniciar o async
método no pool de threads:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
No entanto, esta solução requer um MyAsyncMethod
que funcione no contexto do conjunto de encadeamentos. Portanto, ele não pode atualizar elementos da interface do usuário nem acessar o contexto de solicitação do ASP.NET. E, nesse caso, você também pode adicionar ConfigureAwait(false)
suas await
declarações e usar a solução A.
Atualização, 01/05/2019: As "práticas menos recomendadas" atuais estão em um artigo do MSDN aqui .