Estou aprendendo sobre async / waitit e me deparei com uma situação em que preciso chamar um método assíncrono de forma síncrona. Como eu posso fazer isso?
Método assíncrono:
public async Task<Customers> GetCustomers()
{
return await Service.GetCustomersAsync();
}
Uso normal:
public async void GetCustomers()
{
customerList = await GetCustomers();
}
Eu tentei usar o seguinte:
Task<Customer> task = GetCustomers();
task.Wait()
Task<Customer> task = GetCustomers();
task.RunSynchronously();
Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)
Também tentei uma sugestão a partir daqui , no entanto, ela não funciona quando o expedidor está em um estado suspenso.
public static void WaitWithPumping(this Task task)
{
if (task == null) throw new ArgumentNullException(“task”);
var nestedFrame = new DispatcherFrame();
task.ContinueWith(_ => nestedFrame.Continue = false);
Dispatcher.PushFrame(nestedFrame);
task.Wait();
}
Aqui está a exceção e o rastreamento de pilha da chamada RunSynchronously
:
System.InvalidOperationException
Mensagem : RunSynchronously não pode ser chamado em uma tarefa não acoplada a um delegado.
InnerException : null
Fonte : mscorlib
StackTrace :
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
c#
asynchronous
c#-5.0
async-await
Rachel
fonte
fonte
Task
síncrona; 3) Eu estou integrando GeneXus com MongoDB C motorista # , que expõem alguns métodos só de forma assíncronaSemaphoreSlim
.Respostas:
Aqui está uma solução que eu achei que funciona para todos os casos (incluindo expedidores suspensos). Não é meu código e ainda estou trabalhando para entendê-lo completamente, mas funciona.
Pode ser chamado usando:
customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());
O código é daqui
fonte
DynamicNodeProviderBase
classe base, não é possível declará-lo comoasync
método. Eu tive que substituir por uma nova biblioteca ou simplesmente chamar uma operação síncrona.AspNetSynchronizationContext
, portanto esse hack específico não funcionará se você estiver chamando essas APIs.Esteja ciente de que esta resposta tem três anos. Eu o escrevi com base principalmente em uma experiência com .Net 4.0 e muito pouco com 4.5, especialmente com
async-await
. De um modo geral, é uma solução simples e agradável, mas às vezes quebra as coisas. Por favor, leia a discussão nos comentários..Net 4.5
Apenas use isto:
Consulte: TaskAwaiter , Task.Result , Task.RunSynchronously
.Net 4.0
Usa isto:
...ou isto:
fonte
.Result
pode produzir um impasse em certas do cenárioResult
pode facilmente causar um impasse noasync
código , como eu descrevo no meu blog.Surpreso, ninguém mencionou isso:
Não é tão bonito quanto alguns dos outros métodos aqui, mas possui os seguintes benefícios:
Wait
)AggregateException
(comoResult
)Task
eTask<T>
( experimente você mesmo! )Além disso, como
GetAwaiter
é tipado por pato, isso deve funcionar para qualquer objeto retornado de um método assíncrono (comoConfiguredAwaitable
ouYieldAwaitable
), não apenas para Tarefas.edit: Observe que é possível que essa abordagem (ou uso
.Result
) atinja um impasse, a menos que você adicione.ConfigureAwait(false)
sempre que aguardar, todos os métodos assíncronos que podem ser alcançadosBlahAsync()
(não apenas aqueles que chama diretamente). Explicação .Se você tem preguiça de adicionar em
.ConfigureAwait(false)
qualquer lugar e não se importa com o desempenho, pode fazer alternativamentefonte
GetAwaiter()
: "Este método é destinado ao usuário do compilador, em vez de ser usado diretamente no código".É muito mais simples executar a tarefa no pool de threads, em vez de tentar enganar o agendador para executá-la de forma síncrona. Dessa forma, você pode ter certeza de que não entrará em conflito. O desempenho é afetado devido à mudança de contexto.
fonte
Task.Run(DoSomethingAsync)
? Isso remove um nível de delegados.Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
é mais explícito e aborda a preocupação do @sgnsajgon de que ele possa estar retornando uma tarefa <Task <MyResult>>. A sobrecarga correta do Task.Run é selecionada de qualquer maneira, mas o delegado assíncrono torna sua intenção óbvia.A melhor resposta é você não , com os detalhes dependentes de qual é a "situação".
É um getter / setter de propriedade? Na maioria dos casos, é melhor ter métodos assíncronos do que "propriedades assíncronas". (Para mais informações, consulte minha postagem no blog sobre propriedades assíncronas ).
Este é um aplicativo MVVM e você deseja vincular dados assíncronos? Em seguida, use algo como o meu
NotifyTask
, conforme descrito no meu artigo do MSDN sobre ligação de dados assíncrona .É um construtor? Então você provavelmente deseja considerar um método de fábrica assíncrono. (Para mais informações, consulte minha postagem no blog sobre construtores assíncronos ).
Quase sempre há uma resposta melhor do que fazer a sincronização sobre async.
Se não for possível para a sua situação (e você sabe disso fazendo uma pergunta aqui, descrevendo a situação ), recomendo apenas o uso de código síncrono. Async todo o caminho é o melhor; sincronizar todo o caminho é o segundo melhor. Sincronização por assíncrona não é recomendada.
No entanto, existem várias situações em que a sincronização sobre async é necessária. Especificamente, você está limitado pelo código de chamada de modo que você tem que ser sincronizado (e não têm absolutamente nenhuma maneira de re-pensar ou re-estrutura de seu código para permitir assincronia), e você tem que chamar assíncrono código. Essa é uma situação muito rara, mas surge de tempos em tempos.
Nesse caso, você precisaria usar um dos hacks descritos no meu artigo sobre desenvolvimento de brownfield
async
, especificamente:GetAwaiter().GetResult()
). Observe que isso pode causar conflitos (como eu descrevo no meu blog).Task.Run(..).GetAwaiter().GetResult()
). Observe que isso só funcionará se o código assíncrono puder ser executado em um thread do pool de threads (ou seja, não depende de um contexto de interface do usuário ou ASP.NET).Os loops de mensagens aninhados são os mais perigosos de todos os hacks, porque causam reentrada . A reentrada é extremamente complicada de se pensar e (IMO) é a causa da maioria dos erros de aplicativos no Windows. Em particular, se você estiver no thread da interface do usuário e bloquear uma fila de trabalho (aguardando a conclusão do trabalho assíncrono), o CLR realmente enviará uma mensagem para você - ele realmente tratará algumas mensagens Win32 de dentro do seu código . Ah, e você não tem idéia de quais mensagens - quando Chris Brumme diz: "Não seria ótimo saber exatamente o que será bombeado? Infelizmente, bombear é uma arte negra que está além da compreensão mortal". , então realmente não temos esperança de saber.
Portanto, quando você bloqueia assim em um thread da interface do usuário, está pedindo problemas. Outra cbrumme cita o mesmo artigo: "De tempos em tempos, clientes dentro ou fora da empresa descobrem que estamos enviando mensagens durante o bloqueio gerenciado em um STA [thread da interface do usuário]. Essa é uma preocupação legítima, porque eles sabem que é muito difícil escrever código robusto diante da reentrada ".
Sim, ele é. É muito difícil escrever código robusto diante da reentrada. E os loops de mensagens aninhados forçam você a escrever um código robusto diante da reentrada. É por isso que a resposta aceita (e mais votada) para essa pergunta é extremamente perigosa na prática.
Se você estiver completamente sem todas as outras opções - não poderá reprojetar seu código, não poderá reestruturá-lo para ser assíncrono - será forçado pelo código de chamada imutável a ser sincronizado - não poderá alterar o código downstream para sincronização - você não pode bloquear - você não pode executar o código assíncrono em um thread separado - então e somente então você deve considerar adotar a reentrada.
Se você se encontrar nesse canto, recomendo usar algo como
Dispatcher.PushFrame
para aplicativos WPF , alternar comApplication.DoEvents
aplicativos WinForm e, no caso geral, o meuAsyncContext.Run
.fonte
Main()
método assíncrono não compila; em algum momento, você precisa preencher a lacuna entre os mundos sincronizado e assíncrono. Não é uma " situação muito rara" , é necessária literalmente em todos os programas que chamam um método assíncrono. Não há opção para não "sincronizar sobre async" , apenas uma opção para desviar essa carga do método de chamada, em vez de carregá-lo no que você está escrevendo no momento.async
todos os métodos no meu aplicativo agora. E isso é muito. Isso não pode ser apenas o padrão?Se estou lendo sua pergunta corretamente - o código que deseja a chamada síncrona para um método assíncrono está sendo executado em um encadeamento de expedidor suspenso. E você deseja realmente bloquear esse segmento de forma síncrona até que o método assíncrono seja concluído.
Os métodos assíncronos no C # 5 são alimentados cortando efetivamente o método em pedaços sob o capô e retornando um
Task
que pode rastrear a conclusão geral de todo o shabang. No entanto, a maneira como os métodos cortados são executados pode depender do tipo de expressão transmitida aoawait
operador.Na maioria das vezes, você estará usando
await
uma expressão do tipoTask
. A implementação doawait
padrão da tarefa é "inteligente", pois adia para oSynchronizationContext
, o que basicamente causa o seguinte:await
está em um encadeamento de loop de mensagem Dispatcher ou WinForms, ele garante que os pedaços do método assíncrono ocorram como parte do processamento da fila de mensagens.await
está em um encadeamento de conjuntos de encadeamentos, os pedaços restantes do método assíncrono ocorrem em qualquer lugar no conjunto de encadeamentos.É por isso que você provavelmente está tendo problemas - a implementação do método assíncrono está tentando executar o restante no Dispatcher - mesmo que esteja suspenso.
.... fazendo backup! ....
Eu tenho que fazer a pergunta, por que você está tentando bloquear de forma síncrona um método assíncrono? Fazer isso anularia o propósito de por que o método queria ser chamado de forma assíncrona. Em geral, quando você começa a usar
await
um método Dispatcher ou UI, deseja ativar todo o fluxo da UI de forma assíncrona. Por exemplo, se sua pilha de chamadas era algo como o seguinte:WebRequest.GetResponse()
YourCode.HelperMethod()
YourCode.AnotherMethod()
YourCode.EventHandlerMethod()
[UI Code].Plumbing()
-WPF
ouWinForms
códigoWPF
ouWinForms
Loop de mensagemDepois que o código for transformado para usar assíncrono, você normalmente terminará com
WebRequest.GetResponseAsync()
YourCode.HelperMethodAsync()
YourCode.AnotherMethodAsync()
YourCode.EventHandlerMethodAsync()
[UI Code].Plumbing()
-WPF
ouWinForms
códigoWPF
ouWinForms
Loop de mensagemAtendendo realmente
A classe AsyncHelpers acima realmente funciona porque se comporta como um loop de mensagens aninhadas, mas instala sua própria mecânica paralela no Dispatcher em vez de tentar executar no próprio Dispatcher. Essa é uma solução alternativa para o seu problema.
Outra solução alternativa é executar o método assíncrono em um thread do pool de threads e aguarde a conclusão. Fazer isso é fácil - você pode fazer isso com o seguinte snippet:
A API final será Task.Run (...), mas com o CTP você precisará dos sufixos Ex ( explicação aqui ).
fonte
TaskEx.RunEx(GetCustomers).Result
trava o aplicativo quando ele é executado em um encadeamento de expedidor suspenso. Além disso, o método GetCustomers () normalmente é executado de forma assíncrona; no entanto, em uma situação, ele precisa ser executado de forma síncrona, então eu estava procurando uma maneira de fazer isso sem criar uma versão de sincronização do método.async
métodos adequadamente ; loops aninhados certamente devem ser evitados.Isso está funcionando bem para mim
fonte
MyAsyncMethod().RunTaskSynchronously();
A maneira mais simples de executar tarefas de forma síncrona e sem bloquear o thread da interface do usuário é usar RunSynchronously () como:
No meu caso, eu tenho um evento que é acionado quando algo ocorre. Não sei quantas vezes isso ocorrerá. Então, eu uso o código acima no meu evento; portanto, sempre que é acionado, ele cria uma tarefa. As tarefas são executadas de forma síncrona e funcionam muito bem para mim. Fiquei surpreso que demorei tanto para descobrir isso, considerando como é simples. Geralmente, as recomendações são muito mais complexas e propensas a erros. Foi assim que é simples e limpo.
fonte
Já o enfrentei algumas vezes, principalmente em testes de unidade ou no desenvolvimento de serviços do Windows. Atualmente, eu sempre uso esse recurso:
É simples, fácil e não tive problemas.
fonte
Encontrei esse código no componente Microsoft.AspNet.Identity.Core e funciona.
fonte
Apenas uma pequena nota - esta abordagem:
funciona para o WinRT.
Deixe-me explicar:
Além disso, essa abordagem funciona apenas para soluções da Windows Store!
Nota: Dessa forma, o thread não é seguro se você chamar seu método dentro de outro método assíncrono (de acordo com os comentários do @Servy)
fonte
CancellationToken
a minha solução.No seu código, sua primeira espera pela execução da tarefa, mas você não a iniciou, pelo que aguarda indefinidamente. Tente o seguinte:
Editar:
Você diz que recebe uma exceção. Poste mais detalhes, incluindo rastreamento de pilha.
Mono contém o seguinte caso de teste:
Verifique se isso funciona para você. Se isso não acontecer, apesar de muito improvável, você pode ter uma compilação estranha do CTP assíncrono. Se funcionar, convém examinar o que exatamente o compilador gera e como a
Task
instanciação é diferente desse exemplo.Edição # 2:
Eu verifiquei com o Reflector que a exceção que você descreveu ocorre quando
m_action
énull
. Isso é meio estranho, mas não sou especialista em CTP assíncrono. Como eu disse, você deve descompilar seu código e ver exatamente comoTask
está sendo instanciado qualquer como é que os seusm_action
énull
.PS Qual é o problema dos votos ocasionais? Cuidado ao elaborar?
fonte
RunSynchronously may not be called on a task unbound to a delegate
. Google é nenhuma ajuda desde que todos os resultados para que estão em chinês ...await
palavra-chave é usada. A exceção postada em meu comentário anterior é a exceção que recebo, apesar de ser uma das poucas para as quais não posso pesquisar no Google e encontrar uma causa ou solução.async
e asasync
palavras - chave não passam de açúcar de sintaxe. Compilador gera código para criarTask<Customer>
,GetCustomers()
então é aí que eu procuraria primeiro. Quanto à exceção, você postou apenas a mensagem de exceção, que é inútil sem o tipo de exceção e o rastreamento de pilha. Chame oToString()
método da exceção e publique a saída na pergunta.Testado em .Net 4.6. Também pode evitar um conflito.
Para método assíncrono retornando
Task
.Para método assíncrono retornando
Task<T>
Editar :
Se o chamador estiver em execução no thread do conjunto de encadeamentos (ou o chamador também estiver em uma tarefa), ainda poderá causar um conflito em alguma situação.
fonte
Result
é perfeito para o trabalho se você deseja uma chamada síncrona e, de outra forma, perigoso. Não há nada no nomeResult
ou no sentido intelectualResult
que indique que se trata de uma chamada bloqueada. Realmente deve ser renomeado.use o código abaixo
fonte
Por que não criar uma chamada como:
isso não é assíncrono.
fonte
Esta resposta foi projetada para quem está usando o WPF for .NET 4.5.
Se você tentar executar
Task.Run()
no encadeamento da GUI,task.Wait()
ficará travado indefinidamente, se você não tiver oasync
palavra chave em sua definição de função.Esse método de extensão resolve o problema verificando se estamos no encadeamento da GUI e, se estiver, executando a tarefa no encadeamento do despachante WPF.
Essa classe pode atuar como a cola entre o mundo assíncrono / aguardar e o mundo não assíncrono / aguardar, em situações onde é inevitável, como propriedades do MVVM ou dependências de outras APIs que não usam async / wait.
fonte
Simplesmente ligar
.Result;
ou.Wait()
é um risco de impasse, como muitos disseram nos comentários. Como a maioria de nós gosta de oneliners, você pode usá-los para.Net 4.5<
Adquirindo um valor por meio de um método assíncrono:
Chamando de forma síncrona um método assíncrono
Nenhum problema de deadlock ocorrerá devido ao uso de
Task.Run
.Fonte:
https://stackoverflow.com/a/32429753/3850405
fonte
Penso que o seguinte método auxiliar também pode resolver o problema.
Pode ser usado da seguinte maneira:
fonte
Isso funciona para mim
fonte
Eu descobri que o SpinWait funciona muito bem para isso.
A abordagem acima não precisa usar .Result ou .Wait (). Ele também permite especificar um tempo limite para que você não fique preso para sempre, caso a tarefa nunca seja concluída.
fonte
No wp8:
Embrulhe-o:
Chame-o:
fonte
fonte
Ou você pode apenas ir com:
Para isso compilar, certifique-se de referenciar o conjunto de extensão:
fonte
Tente o seguinte código que funciona para mim:
fonte