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 .


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();

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));
}

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.


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.


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); 
}

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); 
}

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
}

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.