Eu tenho um public async void Foo()
método que eu quero chamar do método síncrono. Até agora, tudo o que vi na documentação do MSDN está chamando métodos assíncronos por métodos assíncronos, mas todo o meu programa não é construído com métodos assíncronos.
Isso é possível?
Aqui está um exemplo de chamada desses métodos de um método assíncrono: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Agora, estou tentando chamar esses métodos assíncronos a partir de métodos de sincronização.
c#
async-await
Torre
fonte
fonte
async void Foo()
método não retorna aTask
, significa que um chamador não pode saber quando é concluído, ele deve retornarTask
.Respostas:
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
:Você não deseja usar
Task.Wait
ouTask.Result
porque eles envolvem exceçõesAggregateException
.Esta solução é apropriada apenas se
MyAsyncMethod
não for sincronizada de volta ao seu contexto. Em outras palavras, todoawait
emMyAsyncMethod
deve terminar comConfigureAwait(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á usarAsyncContext.RunTask
para fornecer um contexto aninhado:* Atualização 14/04/2014: nas versões mais recentes da biblioteca, a API é a seguinte:
(Não há problema em usar
Task.Result
neste exemplo, porqueRunTask
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:Task
.Task
.async
método usaawait
semConfigureAwait
.Task
não pode ser concluído nessa situação porque ele é concluído apenas quando oasync
método é concluído; oasync
método não pode ser concluído porque está tentando agendar sua continuação paraSynchronizationContext
, 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 osasync
métodos.Solução C
AsyncContext.RunTask
não funcionará em todos os cenários. Por exemplo, se oasync
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 oasync
método no pool de threads: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 adicionarConfigureAwait(false)
suasawait
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 .
fonte
WaitAndUnwrapException
é meu próprio método da minha biblioteca AsyncEx . As bibliotecas oficiais do .NET não fornecem muita ajuda para misturar códigos de sincronização e assíncrono (e, em geral, você não deve fazê-lo!). Estou aguardando o .NET 4.5 RTW e um novo laptop que não seja XP antes de atualizar o AsyncEx para rodar no 4.5 (atualmente não posso desenvolver o 4.5 porque estou preso no XP por mais algumas semanas).AsyncContext
agora tem umRun
método que leva uma expressão lambda, então você deve usarvar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
método. A coisa mais próxima que pude encontrar foiRun
, mas isso não tem umaResult
propriedade.var result = AsyncContext.Run(MyAsyncMethod);
Adicionando uma solução que finalmente resolveu meu problema, espero poupar o tempo de alguém.
Leia primeiro alguns artigos de Stephen Cleary :
Das "duas melhores práticas" em "Não bloqueie no código assíncrono", a primeira não funcionou para mim e a segunda não foi aplicável (basicamente, se eu posso usar
await
, eu faço!).Então, aqui está minha solução alternativa: encerre a chamada dentro de um
Task.Run<>(async () => await FunctionAsync());
e esperamos que não haja mais impasse .Aqui está o meu código:
fonte
Task.Run()
não é uma prática recomendada em um código assíncrono. Mas, novamente, qual é a resposta para a pergunta original? Nunca chame um método assíncrono de forma síncrona? Nós desejamos, mas no mundo real, às vezes precisamos.Parallel.ForEach
abuso não terá efeito no 'mundo real' e, eventualmente, derrubou os servidores. Esse código é válido para aplicativos de console, mas como @ChrisPratt diz, não deve ser usado em aplicativos da Web. Pode funcionar "agora", mas não é escalável.A Microsoft criou uma classe AsyncHelper (interna) para executar o Async como Sync. A fonte se parece com:
As classes base Microsoft.AspNet.Identity possuem apenas métodos Async e, para chamá-las como Sincronização, existem classes com métodos de extensão que se parecem com (exemplo de uso):
Para aqueles preocupados com os termos de código de licenciamento, aqui está um link para código muito semelhante (apenas adiciona suporte à cultura no encadeamento) que possui comentários para indicar que ele é licenciado pelo MIT pela Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
fonte
await
chamadasConfigureAwait(false)
. Eu tentei usarAsyncHelper.RunSync
para chamar uma função assíncrona daApplication_Start()
função Global.asax e parece funcionar. Isso significa queAsyncHelper.RunSync
não é propenso ao problema de impasse "marechal de volta ao contexto do chamador" que eu li sobre algum outro lugar nesta postagem?O async Main agora faz parte do C # 7.2 e pode ser ativado nas configurações avançadas de construção do projeto.
Para C # <7.2, a maneira correta é:
Você verá isso usado em muitas documentações da Microsoft, por exemplo: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- tópicos-assinaturas
fonte
MainAsync().Wait()
?Você lê a palavra-chave 'aguardar' como "inicie esta tarefa de longa execução e retorne o controle ao método de chamada". Depois que a tarefa de execução longa é concluída, ela executa o código após ela. O código após a espera é semelhante ao que costumava ser métodos de retorno de chamada. A grande diferença é que o fluxo lógico não é interrompido, o que facilita muito a escrita e a leitura.
fonte
Wait
quebra exceções e tem a possibilidade de um impasse.await
, ele seria executado de forma síncrona. Pelo menos isso funciona para mim (sem ligarmyTask.Wait
). Na verdade, recebi uma exceção quando tentei ligarmyTask.RunSynchronously()
porque já havia sido executado!.Result
.Result
chamada no momento, para que nunca chegue lá. EResult
nunca acaba, porque está esperando alguém que está esperando oResult
fim, basicamente: DNão tenho 100% de certeza, mas acredito que a técnica descrita neste blog deve funcionar em várias circunstâncias:
fonte
No entanto, existe uma boa solução que funciona em (quase: veja comentários) todas as situações: uma bomba de mensagens ad-hoc (SynchronizationContext).
O encadeamento de chamada será bloqueado conforme o esperado, garantindo ainda que todas as continuações chamadas da função assíncrona não entrem em conflito, pois serão empacotadas para o SynchronizationContext (bomba de mensagens) ad-hoc em execução no encadeamento de chamada.
O código do auxiliar de bomba de mensagem ad-hoc:
Uso:
Uma descrição mais detalhada da bomba assíncrona está disponível aqui .
fonte
Para quem mais prestar atenção a esta pergunta ...
Se você olhar,
Microsoft.VisualStudio.Services.WebApi
há uma classe chamadaTaskExtensions
. Dentro dessa classe, você verá o método de extensão estáticaTask.SyncResult()
, que bloqueia totalmente o thread até que a tarefa retorne.Internamente, ele chama, o
task.GetAwaiter().GetResult()
que é bastante simples, mas está sobrecarregado para funcionar em qualquerasync
método que retorneTask
,Task<T>
ouTask<HttpResponseMessage>
... açúcar sintático, bebê ... papai tem um gosto por doces.Parece que
...GetAwaiter().GetResult()
é a maneira oficial da MS de executar código assíncrono em um contexto de bloqueio. Parece funcionar muito bem no meu caso de uso.fonte
Ou use isto:
fonte
Você pode chamar qualquer método assíncrono a partir de código síncrono, ou seja, até precisar
await
deles, caso em que eles também deverão ser marcadosasync
.Como muitas pessoas sugerem aqui, você pode chamar Wait () ou Result na tarefa resultante no seu método síncrono, mas então você acaba com uma chamada de bloqueio nesse método, o que meio que derrota o propósito da assíncrona.
Se você realmente não pode criar seu método
async
e não deseja bloquear o método síncrono, precisará usar um método de retorno de chamada passando-o como parâmetro para o método ContinueWith na tarefa.fonte
async
" desviou minha atenção do que você estava realmente dizendo.Eu sei que estou tão atrasada. Mas no caso de alguém como eu querer resolver isso de maneira organizada, fácil e sem depender de outra biblioteca.
Encontrei o seguinte trecho de código de Ryan
então você pode chamar assim
fonte
Depois de horas tentando métodos diferentes, com mais ou menos sucesso, foi com isso que acabei. Ele não termina em um impasse ao obter resultado e também recebe e lança a exceção original e não a finalizada.
fonte
Pode ser chamado de um novo encadeamento (NÃO do conjunto de encadeamentos!):
fonte
Esses métodos assíncronos do Windows têm um pequeno método bacana chamado AsTask (). Você pode usar isso para que o método retorne como uma tarefa, para que você possa chamar manualmente Wait ().
Por exemplo, em um aplicativo Windows Phone 8 Silverlight, você pode fazer o seguinte:
Espero que isto ajude!
fonte
Se você deseja executá-lo Sync
fonte
RunSynchronously()
uma tarefa quente resulta em umInvalidOperationException
. Experimente com este código:Task.Run(() => {}).RunSynchronously();
fonte