No meu aplicativo de metrô C # / XAML, há um botão que inicia um processo de longa duração. Portanto, como recomendado, estou usando async / waitit para garantir que o thread da interface do usuário não seja bloqueado:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Ocasionalmente, as coisas que acontecem no GetResults exigem entrada adicional do usuário antes que ele possa continuar. Para simplificar, digamos que o usuário apenas tenha que clicar no botão "continuar".
Minha pergunta é: como posso suspender a execução de GetResults de forma que aguarde um evento como o clique de outro botão?
Aqui está uma maneira feia de conseguir o que estou procurando: o manipulador de eventos para o botão continuar "define um sinalizador ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... e o GetResults realiza pesquisas periodicamente:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
A pesquisa é claramente terrível (espera ocupada / desperdício de ciclos) e estou procurando algo baseado em eventos.
Alguma ideia?
Entre neste exemplo simplificado, uma solução seria, obviamente, dividir GetResults () em duas partes, invocar a primeira parte do botão Iniciar e a segunda parte do botão Continuar. Na realidade, as coisas que acontecem no GetResults são mais complexas e diferentes tipos de entrada do usuário podem ser necessários em diferentes pontos da execução. Portanto, dividir a lógica em vários métodos não seria trivial.
ManualResetEvent(Slim)
não parece apoiarWaitAsync()
.async
não significa "roda em um segmento diferente", ou algo assim. Significa apenas "você pode usarawait
neste método". E, nesse caso, o bloqueio internoGetResults()
realmente bloqueará o thread da interface do usuário.await
por si só não garante a criação de outro encadeamento, mas faz com que todo o resto após a instrução seja executado como uma continuaçãoTask
ou aguardável que você chamarawait
. Na maioria das vezes, é algum tipo de operação assíncrona, que pode ser a conclusão de E / S ou algo que esteja em outro encadeamento.SemaphoreSlim.WaitAsync
não basta inserir oWait
thread em um pool de threads.SemaphoreSlim
possui uma fila adequada deTask
s que são usadas para implementarWaitAsync
.Quando você precisa fazer algo incomum
await
, a resposta mais fácil é frequentementeTaskCompletionSource
(ou algumaasync
primitiva ativada com base emTaskCompletionSource
).Nesse caso, sua necessidade é bastante simples, então você pode simplesmente usar
TaskCompletionSource
diretamente:Logicamente,
TaskCompletionSource
é como umasync
ManualResetEvent
, exceto que você só pode "definir" o evento uma vez e o evento pode ter um "resultado" (nesse caso, não o estamos usando, apenas definimos o resultado comonull
).fonte
Aqui está uma classe de utilitário que eu uso:
E aqui está como eu o uso:
fonte
new Task(() => { });
seria concluído instantaneamente?Classe Auxiliar Simples:
Uso:
fonte
example.YourEvent
?Idealmente, você não . Embora você certamente possa bloquear o encadeamento assíncrono, isso é um desperdício de recursos, e não é o ideal.
Considere o exemplo canônico em que o usuário vai almoçar enquanto o botão está aguardando para ser clicado.
Se você interrompeu seu código assíncrono enquanto aguarda a entrada do usuário, estará desperdiçando recursos enquanto o encadeamento estiver em pausa.
Dito isso, é melhor que, em sua operação assíncrona, você defina o estado que precisa manter no ponto em que o botão está ativado e você estará "esperando" com um clique. Nesse ponto, seu
GetResults
método para .Em seguida, quando o botão é clicado, com base no estado que você armazenou, você inicia outra tarefa assíncrona para continuar o trabalho.
Como o
SynchronizationContext
será capturado no manipulador de eventos que chamaGetResults
(o compilador fará isso como resultado do uso daawait
palavra - chave usada e o fato de que SynchronizationContext.Current deve ser não nulo, desde que você esteja em um aplicativo de interface do usuário), você pode usarasync
/await
assim:ContinueToGetResultsAsync
é o método que continua a obter os resultados caso o botão seja pressionado. Se o botão não for pressionado, o manipulador de eventos não fará nada.fonte
GetResults
retorna aTask
.await
simplesmente diz "execute a tarefa e, quando a tarefa estiver concluída, continue o código depois disso". Dado que existe um contexto de sincronização, a chamada é empacotada de volta para o thread da interface do usuário, conforme é capturada noawait
. nãoawait
é o mesmo que , nem um pouco.Task.Wait()
Wait()
. Mas o códigoGetResults()
será executado no thread da interface do usuário aqui, não há outro thread. Em outras palavras, sim,await
basicamente executa a tarefa, como você diz, mas aqui, essa tarefa também é executada no thread da interface do usuário.await
e depois o código depoisawait
, não há bloqueio. O resto do código é empacotado volta na continuação e programado através doSynchronizationContext
.Stephen Toub publicou esta
AsyncManualResetEvent
aula em seu blog .fonte
Com extensões reativas (Rx.Net)
Você pode adicionar o Rx com o Nuget Package System.Reactive
Amostra testada:
fonte
Estou usando minha própria classe AsyncEvent para eventos aguardáveis.
Para declarar um evento na classe que gera eventos:
Para aumentar os eventos:
Para se inscrever nos eventos:
fonte