Suprimindo “aviso CS4014: Como esta chamada não é aguardada, a execução do método atual continua…”

156

Esta não é uma duplicata de "Como chamar com segurança um método assíncrono em C # sem aguardar" .

Como suprimo bem o seguinte aviso?

aviso CS4014: Como essa chamada não é aguardada, a execução do método atual continua antes que a chamada seja concluída. Considere aplicar o operador 'aguardar' ao resultado da chamada.

Um exemplo simples:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

O que eu tentei e não gostei:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Atualizado , desde que a resposta original aceita foi editada, alterei a resposta aceita para a que está usando as devoluções do C # 7.0 , pois acho que não ContinueWithé apropriado aqui. Sempre que preciso registrar exceções para operações de disparar e esquecer, uso uma abordagem mais elaborada proposta por Stephen Cleary aqui .

noseratio
fonte
1
Então, você acha que #pragmanão é legal?
Frédéric Hamidi 25/03
10
@ FrédéricHamidi, eu faço.
noseratio 25/03
2
@ Noseratio: Ah, certo. Desculpe, eu pensei que era o outro aviso. Me ignore!
Jon Skeet
3
@Terribad: Não tenho muita certeza - parece que o aviso é bastante razoável para a maioria dos casos. Em particular, você deve pensar sobre o que você quer que aconteça com quaisquer falhas - geralmente mesmo para "dispare e esqueça", você deve trabalhar para fora como falhas etc. log
Jon Skeet
4
@Terribad, antes de usá-lo dessa maneira ou de outra, você deve ter uma imagem clara de como as exceções são propagadas para métodos assíncronos (verifique isso ). Então, a resposta do @ Knaģis fornece uma maneira elegante de não perder nenhuma exceção para o recurso de ignorar, por meio de um async voidmétodo auxiliar.
Noseratio 21/07

Respostas:

160

Com o C # 7, agora você pode usar descartes :

_ = WorkAsync();
Anthony Wieser
fonte
7
Este é um recurso prático de linguagem pouco que simplesmente não consigo lembrar. É como se houvesse um _ = ...no meu cérebro.
Marc L.
3
Eu encontrei um SupressMessage removido meu aviso de minha Studio "Lista de erros" Visual mas não "saída" e #pragma warning disable CSxxxxparece mais feio do que o descarte;)
David Savage
122

Você pode criar um método de extensão que impedirá o aviso. O método de extensão pode estar vazio ou você pode adicionar um tratamento de exceção .ContinueWith().

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

No entanto, o ASP.NET conta o número de tarefas em execução; portanto, ele não funcionará com a Forget()extensão simples, conforme listado acima, e poderá falhar com a exceção:

Um módulo ou manipulador assíncrono foi concluído enquanto uma operação assíncrona ainda estava pendente.

Com o .NET 4.5.2, ele pode ser resolvido usando HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}
Knaģis
fonte
8
Eu encontrei TplExtensions.Forget. Há muito mais bondade embaixo Microsoft.VisualStudio.Threading. Desejo que ele tenha sido disponibilizado para uso fora do Visual Studio SDK.
noseratio
1
@Noseratio e Knagis, eu gosto dessa abordagem e pretendo usá-la. Eu postei um relacionado de acompanhamento pergunta: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq Qual a finalidade da adição do ConfigureAwait (false) ao Forget ()? Pelo que entendi, o ConfigureAwait afeta apenas a sincronização de threads no momento em que o wait é usado em uma tarefa, mas o objetivo de Forget () é jogar fora a tarefa, para que a tarefa nunca possa ser aguardada, portanto, o ConfigureAwait aqui não faz sentido.
precisa saber é o seguinte
3
Se o encadeamento de desova desaparecer antes que a tarefa de ignorar e concluir seja concluída, sem o ConfigureAwait (false) ele ainda tentará se recompor no encadeamento de desova, esse encadeamento desaparecerá, causando bloqueios. A configuração ConfigureAwait (false) informa ao sistema para não recolocar no encadeamento de chamada.
stricq
2
Esta resposta tem uma edição para gerenciar um caso específico, além de uma dúzia de comentários. Simplesmente as coisas são muitas vezes as certas, vá para as devoluções! E cito a resposta @ fjch1997: É estúpido criar um método que leva mais alguns ticks para executar, apenas com o objetivo de suprimir um aviso.
Teejay
39

Você pode decorar o método com o seguinte atributo:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Basicamente, você está dizendo ao compilador que sabe o que está fazendo e não precisa se preocupar com um possível erro.

A parte importante desse código é o segundo parâmetro. A parte "CS4014:" é o que suprime o aviso. Você pode escrever o que quiser sobre o resto.

Fábio
fonte
Não funciona para mim: Visual Studio for Mac 7.0.1 (build 24). Parece que deveria, mas - não.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]suprime a mensagem na janela de lista de erros, mas a janela de saída ainda mostra uma linha de alerta
David Ching
35

Minha maneira de lidar com isso.

Salve-o em uma variável de descarte (C # 7)

Exemplo

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Desde a introdução das devoluções no C # 7, agora considero que isso é melhor do que suprimir o aviso. Porque não apenas suprime o aviso, mas também torna clara a intenção de disparar e esquecer.

Além disso, o compilador poderá otimizá-lo no modo release.

Apenas suprima-o

#pragma warning disable 4014
...
#pragma warning restore 4014

é uma solução bastante agradável para "disparar e esquecer".

A razão pela qual esse aviso existe é que, em muitos casos, não é sua intenção usar um método que retorne uma tarefa sem aguardá-lo. Suprimir o aviso quando você pretende disparar e esquecer faz sentido.

Se você tiver problemas para se lembrar de como se escreve #pragma warning disable 4014, deixe o Visual Studio adicioná-lo para você. Pressione Ctrl +. para abrir "Ações rápidas" e "Suprimir CS2014"

Contudo

É estúpido criar um método que leva mais alguns ticks para executar, apenas com o objetivo de suprimir um aviso.

fjch1997
fonte
Isso funcionou no Visual Studio para Mac 7.0.1 (compilação 24).
IronRod 6/06/19
1
É estúpido para criar um método que leva um pouco mais carrapatos para executar, apenas com a finalidade de suprimir um aviso - este não adiciona carrapatos extras em tudo e IMO é mais legível:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Muitas vezes quando eu uso AggressiveInliningo compilador simplesmente ignora-lo por qualquer motivo
fjch1997
1
Eu gosto da opção pragma, porque é super simples e se aplica apenas à linha (ou seção) atual de código, não a todo um método.
Wasatchwizard #
2
Não se esqueça de usar o código de erro como #pragma warning disable 4014e depois restaurar o aviso com #pragma warning restore 4014. Ainda funciona sem o código de erro, mas se você não adicionar o número do erro, todas as mensagens serão suprimidas.
DunningKrugerEffect
11

Uma maneira fácil de interromper o aviso é simplesmente atribuir a tarefa ao chamá-lo:

Task fireAndForget = WorkAsync(); // No warning now

E assim, em sua postagem original, você faria:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
noelicus
fonte
Mencionei essa abordagem na própria pergunta, como uma das que eu particularmente não gostei.
21317 noseratio
Ops! Não percebi isso porque estava na mesma seção de código do seu pragma ... E eu estava procurando respostas. Além disso, o que você não gosta nesse método?
Noelicus 10/03
1
Não gosto que isso taskpareça uma variável local esquecida. Quase como o compilador deveria me dar outro aviso, algo como " taské atribuído, mas seu valor nunca é usado", além disso, não. Além disso, torna o código menos legível. Eu mesmo uso essa abordagem.
Noseratio 10/03
É justo - tive um sentimento semelhante e é por isso que o nomeio fireAndForget... então espero que seja doravante sem referência.
Noelicus
4

O motivo do aviso é que o WorkAsync está retornando um Taskque nunca é lido ou aguardado. Você pode definir o tipo de retorno do WorkAsync para voide o aviso desaparecerá.

Normalmente, um método retorna a Taskquando o chamador precisa saber o status do trabalhador. No caso de um recurso de ignorar, deve-se retornar o nulo para parecer que o chamador é independente do método chamado.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
Vencedor
fonte
2

Por que não agrupá-lo dentro de um método assíncrono que retorna nulo? Um pouco longo, mas todas as variáveis ​​são usadas.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
fonte
1

Encontrei essa abordagem por acidente hoje. Você pode definir um delegado e atribuir o método assíncrono ao delegado primeiro.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

e chame assim

(new IntermediateHandler(AsyncOperation))();

...

Eu pensei que era interessante que o compilador não desse exatamente o mesmo aviso ao usar o delegado.

David Beavon
fonte
Não há necessidade de declarar um delegado, você também pode fazê- (new Func<Task>(AsyncOperation))()lo, embora a IMO ainda seja um pouco detalhada.
Noseratio 6/12