Suppression de "avertissement CS4014: Cet appel n'étant pas attendu, l'exécution de la méthode actuelle continue…"

156

Ce n'est pas un doublon de "Comment appeler en toute sécurité une méthode async en C # sans attendre" .

Comment supprimer gentiment l'avertissement suivant?

avertissement CS4014: Cet appel n'étant pas attendu, l'exécution de la méthode actuelle se poursuit avant la fin de l'appel. Envisagez d'appliquer l'opérateur 'await' au résultat de l'appel.

Un exemple simple:

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

Ce que j'ai essayé et je n'ai pas aimé:

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
    // ...
}

Mis à jour , puisque la réponse acceptée d'origine a été modifiée, j'ai changé la réponse acceptée par celle utilisant les rejets C # 7.0 , car je ne pense pas que ce ContinueWithsoit approprié ici. Chaque fois que j'ai besoin de consigner des exceptions pour des opérations d'incendie et d'oubli, j'utilise une approche plus élaborée proposée par Stephen Cleary ici .

noseratio
la source
1
Alors, vous pensez que ce #pragman'est pas gentil?
Frédéric Hamidi
10
@ FrédéricHamidi, je le fais.
noseratio
2
@Noseratio: Ah, c'est vrai. Désolé, je pensais que c'était l'autre avertissement. Ignore moi!
Jon Skeet
3
@Terribad: Je ne suis pas vraiment sûr - il semble que l'avertissement soit assez raisonnable dans la plupart des cas. En particulier, vous devriez penser à ce que vous voulez qu'il advienne des échecs - généralement même pour "feu et oubliez", vous devriez trouver comment consigner les échecs, etc.
Jon Skeet
4
@Terribad, avant de l'utiliser de cette manière ou d'une autre, vous devriez avoir une image claire de la façon dont les exceptions sont propagées pour les méthodes asynchrones (vérifiez ceci ). Ensuite, la réponse de @ Knaģis fournit un moyen élégant de ne pas perdre d'exceptions pour le feu et l'oubli, via une async voidméthode d'assistance.
noseratio

Réponses:

160

Avec C # 7, vous pouvez désormais utiliser les rejets :

_ = WorkAsync();
Anthony Wieser
la source
7
C'est une petite fonctionnalité de langage pratique dont je ne me souviens tout simplement pas. C'est comme s'il y avait un _ = ...dans mon cerveau.
Marc L.
3
J'ai trouvé un SupressMessage supprimé mon avertissement de ma "Liste d'erreurs" de Visual Studio mais pas de "Sortie" et #pragma warning disable CSxxxxsemble plus moche que le rejet;)
David Savage
122

Vous pouvez créer une méthode d'extension qui empêchera l'avertissement. La méthode d'extension peut être vide ou vous pouvez y ajouter la gestion des exceptions .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();
}

Cependant, ASP.NET compte le nombre de tâches en cours d'exécution, il ne fonctionnera donc pas avec l' Forget()extension simple comme indiqué ci-dessus et peut échouer à la place avec l'exception:

Un module ou un gestionnaire asynchrone s'est terminé alors qu'une opération asynchrone était toujours en attente.

Avec .NET 4.5.2, il peut être résolu en utilisant 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
la source
8
J'ai trouvé TplExtensions.Forget. Il y a beaucoup plus de bonté sous Microsoft.VisualStudio.Threading. Je souhaite qu'il soit rendu disponible pour une utilisation en dehors du SDK Visual Studio.
noseratio
1
@Noseratio et Knagis, j'aime cette approche et j'ai l'intention de l'utiliser. J'ai posté une question de suivi connexe: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq À quoi servirait l'ajout de ConfigureAwait (false) à Forget ()? Si je comprends bien, ConfigureAwait n'affecte la synchronisation des threads qu'au moment où await est utilisé sur une tâche, mais le but de Forget () est de jeter la tâche, de sorte que la tâche ne peut jamais être attendue, donc ConfigureAwait ici est inutile.
dthorpe le
3
Si le thread de génération disparaît avant que la tâche d'incendie et d'oubli ne soit terminée, sans ConfigureAwait (false), il essaiera toujours de se réorganiser sur le thread de génération, ce thread est parti donc il se bloque. La définition de ConfigureAwait (false) indique au système de ne pas retourner sur le thread appelant.
stricq
2
Cette réponse a une modification pour gérer un cas spécifique, plus une douzaine de commentaires. Les choses simplement sont souvent les bonnes choses, optez pour les rejets! Et je cite la réponse @ fjch1997: C'est stupide de créer une méthode qui prend quelques ticks de plus à exécuter, juste dans le but de supprimer un avertissement.
Teejay
39

Vous pouvez décorer la méthode avec l'attribut suivant:

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

En gros, vous dites au compilateur que vous savez ce que vous faites et qu'il n'a pas besoin de s'inquiéter d'une éventuelle erreur.

La partie importante de ce code est le deuxième paramètre. La partie "CS4014:" supprime l'avertissement. Vous pouvez écrire tout ce que vous voulez sur le reste.

Fábio
la source
Ne fonctionne pas pour moi: Visual Studio pour Mac 7.0.1 (build 24). On dirait que ça devrait mais - non.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]supprime le message dans la fenêtre Liste des erreurs, mais la fenêtre Sortie affiche toujours une ligne d'avertissement
David Ching
35

Mes deux façons de gérer cela.

Enregistrez-le dans une variable d'élimination (C # 7)

Exemple

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

Depuis l'introduction des rejets dans C # 7, je considère maintenant que c'est mieux que de supprimer l'avertissement. Parce qu'il supprime non seulement l'avertissement, mais rend également clair l'intention d'incendier et d'oublier.

De plus, le compilateur pourra l'optimiser en mode version.

Supprimez-le simplement

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

est une solution assez sympa pour "tirer et oublier".

La raison pour laquelle cet avertissement existe est que dans de nombreux cas, vous n'avez pas l'intention d'utiliser une méthode qui renvoie une tâche sans l'attendre. Il est logique de supprimer l'avertissement lorsque vous avez l'intention de tirer et d'oublier.

Si vous ne parvenez pas à vous souvenir de l'orthographe #pragma warning disable 4014, laissez simplement Visual Studio l'ajouter pour vous. Appuyez sur Ctrl +. pour ouvrir "Actions rapides", puis "Supprimer CS2014"

En tout

C'est stupide de créer une méthode qui prend quelques ticks de plus à exécuter, juste dans le but de supprimer un avertissement.

fjch1997
la source
Cela fonctionnait dans Visual Studio pour Mac 7.0.1 (build 24).
IronRod du
1
C'est stupide de créer une méthode qui prend quelques ticks de plus à exécuter, juste dans le but de supprimer un avertissement - celui-ci n'ajoute pas du tout de ticks supplémentaires et IMO est plus lisible:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Souvent, lorsque j'utilise AggressiveInliningle compilateur, je l'ignore pour une raison quelconque
fjch1997
1
J'aime l'option pragma, car elle est super simple et ne s'applique qu'à la ligne (ou section) actuelle du code, pas à une méthode entière.
wasatchwizard
2
N'oubliez pas d'utiliser le code d'erreur comme #pragma warning disable 4014, puis de restaurer l'avertissement par la suite avec #pragma warning restore 4014. Cela fonctionne toujours sans le code d'erreur, mais si vous n'ajoutez pas le numéro d'erreur, tous les messages seront supprimés.
DunningKrugerEffect
11

Un moyen simple d'arrêter l'avertissement est d'attribuer simplement la tâche lors de son appel:

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

Et donc dans votre message d'origine, vous feriez:

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
la source
J'ai mentionné cette approche dans la question elle-même, comme une de celles que je n'aimais pas particulièrement.
noseratio
Oups! Je n'ai pas remarqué cela parce que c'était dans la même section de code que votre pragma ... Et je cherchais des réponses. A part ça, qu'est-ce que tu n'aimes pas dans cette méthode?
noelicus
1
Je n'aime pas que cela taskressemble à une variable locale oubliée. Presque comme le compilateur devrait me donner un autre avertissement, quelque chose comme " taskest assigné mais sa valeur n'est jamais utilisée", d'ailleurs ce n'est pas le cas. En outre, cela rend le code moins lisible. J'utilise moi-même cette approche.
noseratio
Assez juste - j'ai eu un sentiment similaire, c'est pourquoi je le nomme fireAndForget... alors je m'attends à ce qu'il ne soit plus référencé.
noelicus
4

La raison de l'avertissement est que WorkAsync renvoie un Taskqui n'est jamais lu ou attendu. Vous pouvez définir le type de retour de WorkAsync sur voidet l'avertissement disparaîtra.

En général, une méthode renvoie un Tasklorsque l'appelant a besoin de connaître l'état du travailleur. Dans le cas d'un feu et oubliez, void doit être retourné pour ressembler à ce que l'appelant est indépendant de la méthode appelée.

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); 
}
Victor
la source
2

Pourquoi ne pas l'envelopper dans une méthode asynchrone qui renvoie void? Un peu long mais toutes les variables sont utilisées.

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

J'ai trouvé cette approche par accident aujourd'hui. Vous pouvez définir un délégué et attribuer d'abord la méthode async au délégué.

    delegate Task IntermediateHandler();



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

et appelle ça comme ça

(new IntermediateHandler(AsyncOperation))();

...

J'ai pensé qu'il était intéressant que le compilateur ne donne pas exactement le même avertissement lors de l'utilisation du délégué.

David Beavon
la source
Pas besoin de déclarer un délégué, autant faire (new Func<Task>(AsyncOperation))()bien que l'OMI soit encore un peu trop verbeux.
noseratio