avisando que esta chamada não é aguardada, a execução do método atual continua

135

Acabei de adquirir o VS2012 e tentar entender async.

Digamos que eu tenho um método que busca algum valor de uma fonte de bloqueio. Não quero que o chamador do método seja bloqueado. Eu poderia escrever o método para receber um retorno de chamada que é chamado quando o valor chega, mas como estou usando o C # 5, decido tornar o método assíncrono para que os chamadores não precisem lidar com retornos de chamada:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Aqui está um método de exemplo que o chama. Se PromptForStringAsyncnão fosse assíncrono, esse método exigiria o aninhamento de um retorno de chamada em um retorno de chamada. Com o async, escrevo meu método dessa maneira muito natural:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Por enquanto, tudo bem. O problema é quando eu chamo GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

O ponto principal GetNameAsyncé que é assíncrono. Não quero que ele bloqueie, porque quero voltar para o MainWorkOfApplicationIDontWantBlocked o mais rápido possível e permitir que GetNameAsync faça suas coisas em segundo plano. No entanto, chamar dessa maneira me dá um aviso do compilador na GetNameAsynclinha:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Estou perfeitamente ciente de que "a execução do método atual continua antes que a chamada seja concluída". Esse é o objetivo do código assíncrono, certo?

Prefiro que meu código seja compilado sem avisos, mas não há nada para "corrigir" aqui, porque o código está fazendo exatamente o que pretendo fazer. Posso me livrar do aviso armazenando o valor de retorno de GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Mas agora eu tenho código supérfluo. O Visual Studio parece entender que fui forçado a escrever esse código desnecessário, porque suprime o aviso "valor nunca usado" normal.

Também posso me livrar do aviso envolvendo GetNameAsync em um método que não é assíncrono:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Mas esse é um código ainda mais supérfluo. Portanto, tenho que escrever um código que não preciso ou tolero um aviso desnecessário.

Existe algo sobre meu uso de assíncrono errado aqui?

Lama
fonte
2
BTW, ao implementar, PromptForStringAsyncvocê trabalha mais do que precisa; basta retornar o resultado de Task.Factory.StartNew. Já é uma tarefa cujo valor é a sequência inserida no console. Não há necessidade de esperar o retorno do resultado; isso não agrega nenhum novo valor.
Servy
Wwouldn't faz mais sentido para GetNameAsyncfornecer o nome completo que foi fornecida pelo usuário (ou seja Task<Name>, ao invés de apenas retornar um Task? DoStuffPoderia, então, armazenar essa tarefa, e quer awaitque após o outro método, ou mesmo passar a tarefa para a outra método para que ele pudesse awaitou Waitem algum lugar dentro dele de implementação.
Servy
@ Service: Se eu apenas retornar a tarefa, recebo um erro "Como esse é um método assíncrono, a expressão de retorno deve ser do tipo 'string' em vez de 'Task <string>'".
Mud
1
Remova a asyncpalavra - chave
Servy
13
Na IMO, essa foi uma má escolha para um aviso por parte da equipe C #. Os avisos devem ser para coisas que quase certamente estão erradas. Existem muitos casos em que você deseja "disparar e esquecer" um método assíncrono e outras vezes em que realmente deseja aguardá-lo.
MgSam

Respostas:

103

Se você realmente não precisa do resultado, pode simplesmente mudar a GetNameAsyncassinatura para retornar void:

public static async void GetNameAsync()
{
    ...
}

Considere a resposta a uma pergunta relacionada: Qual é a diferença entre retornar nulo e retornar uma tarefa?

Atualizar

Se você precisar do resultado, poderá alterar GetNameAsyncpara retornar, digamos Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

E use-o da seguinte maneira:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}
Nikolay Khil
fonte
15
Esse seria o caso apenas se ele nunca se importa com o resultado, em vez de simplesmente não precisar do resultado desta vez .
Servy
3
@ Servy, certo, obrigado pelo esclarecimento. Mas os OPs GetNameAsyncnão retornam nenhum valor (exceto o resultado em si, é claro).
Nikolay Khil
2
Correto, mas retornando uma tarefa, ele pode saber quando termina a execução de todas as operações assíncronas. Se retornar void, ele não tem como saber quando está pronto. Foi isso que eu quis dizer quando disse "o resultado" no meu comentário anterior.
Servy
29
Como regra geral, você nunca deve ter um async voidmétodo, exceto para manipuladores de eventos.
Daniel Mann
2
Outra coisa a observar aqui é que o comportamento é diferente quando se trata de exceções não observadas quando você alterna para async voidqualquer exceção que você não captura travará o processo, mas no .net 4.5 ele continuará em execução.
Caleb Vear
62

Estou muito atrasado para esta discussão, mas também há a opção de usar a #pragmadiretiva pré-processador. Eu tenho um código assíncrono aqui e ali que, explicitamente, não quero aguardar em algumas condições, e não gosto de avisos e variáveis ​​não utilizadas, assim como o resto de vocês:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

O "4014"vem desta página MSDN: Compiler Aviso (nível 1) CS4014 .

Consulte também o aviso / resposta de @ ryan-horath aqui https://stackoverflow.com/a/12145047/928483 .

Exceções lançadas durante uma chamada assíncrona que não é esperada serão perdidas. Para se livrar desse aviso, você deve atribuir o valor de retorno de tarefa da chamada assíncrona a uma variável. Isso garante que você tenha acesso a quaisquer exceções lançadas, que serão indicadas no valor de retorno.

Atualização para C # 7.0

O C # 7.0 adiciona um novo recurso, descarte variáveis: Descartes - Guia de C # , que também pode ajudar nesse sentido.

_ = SomeMethodAsync();
Willem
fonte
5
Isso é absolutamente incrível. Eu não tinha ideia de que você poderia fazer isso. Obrigado!
Maxim Gershkovich
1
Nem é necessário dizer var, basta escrever_ = SomeMethodAsync();
Ray
41

Não gosto particularmente das soluções que atribuem a tarefa a uma variável não utilizada ou alteram a assinatura do método para retornar nulo. O primeiro cria código supérfluo e não intuitivo, enquanto o último pode não ser possível se você estiver implementando uma interface ou tiver outro uso da função em que deseja usar a tarefa retornada.

Minha solução é criar um método de extensão da tarefa, chamado DoNotAwait () que não faz nada. Isso não apenas suprimirá todos os avisos, ReSharper ou outros, mas também tornará o código mais compreensível e indicará aos futuros mantenedores do seu código que você realmente pretendia que a chamada não fosse aguardada.

Método de extensão:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Uso:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Editado para adicionar: isso é semelhante à solução de Jonathan Allen, em que o método de extensão iniciaria a tarefa se ainda não tiver sido iniciado, mas eu prefiro ter funções de finalidade única para que a intenção do chamador seja completamente clara.

Joe Savage
fonte
1
Eu gosto disso, mas renomei-o para Unawait ();)
Ostati 2/18
29

async void É RUIM!

  1. Qual é a diferença entre retornar nulo e retornar uma tarefa?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

O que eu sugiro é que você execute explicitamente o Taskmétodo anônimo ...

por exemplo

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Ou, se você quiser bloquear, aguarde o método anônimo

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

No entanto, se o seu GetNameAsyncmétodo precisar interagir com a interface do usuário ou com qualquer coisa vinculada à interface do usuário (WINRT / MVVM, estou olhando para você), ficará um pouco mais engraçado =)

Você precisará passar a referência ao expedidor da interface do usuário desta maneira ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

E então, no seu método assíncrono, você precisará interagir com a interface do usuário ou com os elementos vinculados à interface do usuário, como o expedidor ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });
MrEs
fonte
Sua extensão de tarefas é incrível. Não sei por que não implementei um antes.
Mikael Dúi Bolinder 13/09
8
A primeira abordagem mencionada causa um aviso diferente: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. Isso também cria um novo encadeamento, enquanto um novo encadeamento não será necessariamente criado apenas com async / waitit.
gregsdennis
Você deveria acordar, senhor!
Ozkan
16

Isto é o que estou fazendo atualmente:

SomeAyncFunction().RunConcurrently();

Onde RunConcurrentlyé definido como ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/

Jonathan Allen
fonte
1
+1. Parece que evita todos os problemas comparados nos comentários de outras respostas. Obrigado.
Grault 20/03/14
3
Você só precisa disso: public static void Forget(this Task task) { }
Shital Shah
1
o que você quer dizer com @ShitalShah
Jay Wick
@ShitalShah que funcionará apenas com tarefas de inicialização automática, como aquelas criadas com async Task. Algumas tarefas precisam ser iniciadas manualmente.
Jonathan Allen
7

De acordo com o artigo da Microsoft sobre este aviso, você pode resolvê-lo simplesmente atribuindo a tarefa retornada a uma variável. Abaixo está uma tradução do código fornecido no exemplo da Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Observe que isso resultará na mensagem "A variável local nunca é usada" no ReSharper.

devlord
fonte
Sim, isso suprime o aviso, mas não, isso realmente não resolve nada. TaskAs funções de retorno devem ser await-ed, a menos que você tenha uma boa razão para não fazê- lo. Não há nenhuma razão aqui para descartar a tarefa seria melhor do que a resposta já aceita do uso de um async voidmétodo.
Eu também prefiro o método nulo. Isso torna o StackOverflow mais inteligente que a Microsoft, mas é claro que isso deve ser dado.
Devlord # 29/13
4
async voidapresenta problemas sérios no tratamento de erros e resulta em código não testável (consulte meu artigo do MSDN ). Seria muito melhor usar a variável - se você tiver certeza absoluta de que deseja que as exceções sejam engolidas silenciosamente. O mais provável é que o op queira iniciar dois se Taskdepois executar um await Task.WhenAll.
Stephen Cleary
@StephenCleary Se as exceções de tarefas não observadas são ignoradas, é configurável. Se houver alguma chance de seu código ser usado no projeto de outra pessoa, não deixe isso acontecer. Um async void DoNotWait(Task t) { await t; }método auxiliar simples pode ser usado para evitar as desvantagens dos async voidmétodos que você descreve. (E eu não acho que Task.WhenAllé o que o OP quer, mas poderia muito bem ser.)
@ DVD: Seu método auxiliar o tornaria testável, mas ainda teria um suporte de manipulação de exceção muito pobre.
Stephen Cleary
3

Aqui, uma solução simples.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

Saudações

JuanluElGuerre
fonte
1

É o seu exemplo simplificado que causa o código supérfluo. Normalmente, você deseja usar os dados que foram buscados a partir da fonte de bloqueio em algum momento do programa, para que você deseje o resultado de volta para que seja possível acessar os dados.

Se você realmente tem algo que acontece totalmente isolado do resto do programa, a assíncrona não seria a abordagem correta. Basta iniciar um novo thread para essa tarefa.

Guffa
fonte
I fazer quiser usar os dados obtido a partir da fonte de bloqueio, mas eu não quero bloquear o chamador enquanto espero por isso. Sem async, você pode fazer isso transmitindo um retorno de chamada. No meu exemplo, tenho dois métodos assíncronos que precisam ser chamados em sequência e a segunda chamada precisa usar um valor retornado pela primeira. Isso significaria aninhar manipuladores de retorno de chamada, que fica feio como o inferno. A documentação Estive lendo sugere este é especificamente o que asyncfoi projetado para limpar ( por exemplo )
Mud
@Ud: Basta enviar o resultado da primeira chamada assíncrona como parâmetro para a segunda chamada. Dessa forma, o código no segundo método inicia imediatamente e pode aguardar o resultado da primeira chamada quando lhe apetecer.
Guffa
Eu posso fazer isso, mas sem assíncrono, isso significa retornos de chamada aninhados, assim: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Mesmo neste exemplo trivial, é muito chato analisar. Com assíncrona, código equivalente é gerado para mim quando eu escrevo result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); O que é muito mais fácil de ler (ainda que nem são muito legível esmagado juntos em este comentário!)
Mud
@Ud: Sim. Mas você pode chamar o segundo método assíncrono logo após o primeiro, não há motivo para aguardar a chamada síncrona Use.
Guffa
Eu realmente não entendo o que você está dizendo. MethodWithCallback é assíncrono. Se eu ligar para eles sem esperar na primeira chamada, a segunda poderá terminar antes da primeira. No entanto, preciso do resultado da primeira chamada no manipulador para a segunda. Então eu tenho que esperar na primeira ligação.
Mud
0

Deseja realmente ignorar o resultado? como na inclusão de ignorar exceções inesperadas?

Caso contrário, você pode querer dar uma olhada nesta pergunta: Abordar o fogo e esquecer ,

Jens
fonte
0

Se você não deseja alterar a assinatura do método para retornar void(como retornar voidsempre deve ser um nulo ), você pode usar o recurso de descarte do C # 7.0+ como este, que é um pouco melhor do que atribuir a uma variável (e deve remover a maioria dos outros avisos das ferramentas de validação de fonte):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Simon Mourier
fonte