Devo evitar manipuladores de eventos 'async void'?

118

Eu sei que geralmente é considerado uma má ideia usar async voidmétodos de disparar e esquecer para iniciar tarefas, porque não há controle da tarefa pendente e é complicado lidar com exceções que podem ser lançadas dentro de tal método.

Em geral, devo evitar async voidmanipuladores de eventos também? Por exemplo,

private async void Form_Load(object sender, System.EventArgs e)
{
        await Task.Delay(2000); // do async work
        // ...
} 

Posso reescrever assim:

Task onFormLoadTask = null; // track the task, can implement cancellation

private void Form_Load(object sender, System.EventArgs e)
{
        this.onFormLoadTask = OnFormLoadTaskAsync(sender, e);
} 

private async Task OnFormLoadTaskAsync(object sender, System.EventArgs e)
{
        await Task.Delay(2000); // do async work
        // ...
} 

Quais são as rochas subaquáticas para manipuladores de eventos assíncronos, além da possível reentrância?

avo
fonte
Você deveria, mas você não pode. Além disso, todos os cuidados que você deve tomar ao usar async void já são exigidos pelos manipuladores de eventos da IU.
Paulo Morgado,
E a reentrada acontece por causa de operações assíncronas disparadas pelo manipulador de eventos e não pelo uso de async-await por si só.
Paulo Morgado,

Respostas:

152

A diretriz é evitar, async void exceto quando usado em um manipulador de eventos, portanto, usar async voidem um manipulador de eventos está OK.

Dito isso, por motivos de teste de unidade , geralmente gosto de fatorar a lógica de todos os async voidmétodos. Por exemplo,

public async Task OnFormLoadAsync(object sender, EventArgs e)
{
  await Task.Delay(2000);
  ...
}

private async void Form_Load(object sender, EventArgs e)
{
  await OnFormLoadAsync(sender, e);
}
Stephen Cleary
fonte
Estou curioso ... há uma razão pela qual você simplesmente não muda Form_Loado acesso public? Parece que o código seria menos detalhado dessa forma.
InteXX
Opa, deixa pra lá ... VBer tentando ler C # aqui ... Acabei de notar o tipo de retorno de OnFormLoadAsync. Vejo agora que isso é um truque útil. Obrigado.
InteXX
Dito isso, você poderia dar uma olhada e dar uma opinião aqui . Obrigado!
InteXX
2
@AlexHopeO'Connor: O Handledsinalizador deve ser definido de forma síncrona; não é possível usar asyncpara tomar uma decisão sobre se o evento é tratado ou não.
Stephen Cleary
1
@AlexHopeO'Connor: Já faz um tempo que não trabalho com um aplicativo WPF, mas já usei soluções semelhantes a essa no passado. Ou seja, faça o ICommand.Executemétodo async void; Eu considero isso aceitável, pois ICommand.Executeé logicamente um manipulador de eventos.
Stephen Cleary
49

Devo geralmente evitar manipuladores de eventos void assíncronos também?

Geralmente, os manipuladores de eventos são o único caso em que um método void async não é um cheiro de código potencial.

Agora, se você precisa rastrear a tarefa por algum motivo, a técnica que você descreve é ​​perfeitamente razoável.

Eric Lippert
fonte
6

Sim, geralmente assíncrono sem manipuladores de eventos é o único caso. Se quiser saber mais sobre isso, assista a um ótimo vídeo aqui no canal 9

The only case where this kind of fire-and-forget is appropriate is in top-level event-handlers. Every other async method in your code should return "async Task".

aqui está o link

Idrees Khan
fonte
Os ' manipuladores de eventos de nível superior ' são uma dica importante. Ao usar o manipulador de eventos void assíncrono em um manipulador de eventos de nível inferior, isso pode causar grandes problemas com exceções não detectadas.
Portikus
Obrigado pelo link do vídeo, muito útil
lsp
5

Se você usa o ReSharper, uma extensão ReCommended gratuita pode ser útil para você. Ele analisa os métodos de "vazio assíncrono" e realça quando usado de forma inadequada. A extensão pode distinguir diferentes usos de void assíncrono e fornecer soluções rápidas apropriadas descritas aqui: wiki ReCommended-Extension .

Alexander Zwitbaum
fonte