Unterdrücken von "Warnung CS4014: Da dieser Aufruf nicht erwartet wird, wird die Ausführung der aktuellen Methode fortgesetzt ..."

156

Dies ist kein Duplikat von "So rufen Sie eine asynchrone Methode sicher in C # auf, ohne zu warten" .

Wie unterdrücke ich die folgende Warnung?

Warnung CS4014: Da dieser Aufruf nicht erwartet wird, wird die Ausführung der aktuellen Methode fortgesetzt, bevor der Aufruf abgeschlossen ist. Wenden Sie den Operator "Warten" auf das Ergebnis des Anrufs an.

Ein einfaches Beispiel:

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

Was ich versucht habe und nicht mochte:

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

Aktualisiert , da die ursprünglich akzeptierte Antwort bearbeitet wurde, habe ich die akzeptierte Antwort in die Antwort mit C # 7.0-Rückwürfen geändert , da ich dies hier nicht für ContinueWithangemessen halte . Wann immer ich Ausnahmen für Feuer-und-Vergessen-Operationen protokollieren muss, verwende ich einen ausgefeilteren Ansatz, den Stephen Cleary hier vorgeschlagen hat .

noseratio
quelle
1
Also, denkst du #pragmaist nicht schön?
Frédéric Hamidi
10
@ FrédéricHamidi, das tue ich.
Noseratio
2
@Noseratio: Ah, richtig. Entschuldigung, ich hatte gedacht, es wäre die andere Warnung. Ignoriere mich!
Jon Skeet
3
@ Ferribad: Ich bin mir nicht sicher - es scheint, dass die Warnung für die meisten Fälle ziemlich vernünftig ist. Insbesondere sollten Sie darüber nachdenken, was mit Fehlern geschehen soll - normalerweise sollten Sie sogar bei "Feuer und Vergessen" herausfinden, wie Fehler usw. protokolliert werden
Jon Skeet,
4
@Terribad, bevor Sie es auf die eine oder andere Weise verwenden, sollten Sie ein klares Bild davon haben, wie Ausnahmen für asynchrone Methoden weitergegeben werden (überprüfen Sie dies ). Dann liefern die Antwort von @ Knaģis eine elegante Art und Weise von nicht alle Ausnahmen für verlieren Feuer und vergessen, über eine async voidHilfsmethode.
Noseratio

Antworten:

160

Mit C # 7 können Sie jetzt Rückwürfe verwenden :

_ = WorkAsync();
Anthony Wieser
quelle
7
Dies ist eine praktische kleine Sprachfunktion, an die ich mich einfach nicht erinnern kann. Es ist, als wäre da ein _ = ...in meinem Gehirn.
Marc L.
3
Ich habe festgestellt, dass eine SupressMessage meine Warnung aus meiner Visual Studio "Fehlerliste", aber nicht aus "Ausgabe" entfernt hat und #pragma warning disable CSxxxxhässlicher aussieht als das Verwerfen;)
David Savage
122

Sie können eine Erweiterungsmethode erstellen, die die Warnung verhindert. Die Erweiterungsmethode kann leer sein oder Sie können dort eine Ausnahmebehandlung hinzufügen .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();
}

jedoch ASP.NET zählt die Anzahl der laufenden Tasks, so wird es nicht funktionieren mit der einfachen Forget()Erweiterung wie oben aufgeführt und stattdessen mit Ausnahme fehlschlagen:

Ein asynchrones Modul oder Handler wurde abgeschlossen, während eine asynchrone Operation noch ausstand.

Mit .NET 4.5.2 kann es gelöst werden mit 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
quelle
8
Ich habe gefunden TplExtensions.Forget. Es gibt viel mehr Güte unter Microsoft.VisualStudio.Threading. Ich wünschte, es wäre für die Verwendung außerhalb von Visual Studio SDK verfügbar.
Noseratio
1
@Noseratio und Knagis, ich mag diesen Ansatz und ich plane, ihn zu verwenden. Ich habe eine verwandte Folgefrage gepostet: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq Welchen Zweck würde das Hinzufügen von ConfigureAwait (false) zu Forget () erfüllen? Soweit ich weiß, wirkt sich ConfigureAwait nur auf die Thread-Synchronisierung an dem Punkt aus, an dem das Warten auf eine Aufgabe verwendet wird. Der Zweck von Forget () besteht jedoch darin, die Aufgabe wegzuwerfen, sodass die Aufgabe niemals erwartet werden kann. Daher ist ConfigureAwait hier sinnlos.
Dthorpe
3
Wenn der Laich-Thread verschwindet, bevor die Feuer- und Vergessensaufgabe abgeschlossen ist, wird er ohne ConfigureAwait (false) weiterhin versuchen, sich wieder auf den Laich-Thread zu setzen. Dieser Thread ist verschwunden, sodass er blockiert. Durch Festlegen von ConfigureAwait (false) wird das System angewiesen, nicht auf den aufrufenden Thread zurückzugreifen.
Stricq
2
Diese Antwort enthält eine Bearbeitung zum Verwalten eines bestimmten Falls sowie ein Dutzend Kommentare. Einfach Dinge sind oft die richtigen Dinge, machen Sie Rückwürfe! Und ich zitiere die Antwort von @ fjch1997: Es ist dumm, eine Methode zu erstellen, deren Ausführung einige weitere Ticks erfordert, nur um eine Warnung zu unterdrücken.
Teejay
39

Sie können die Methode mit dem folgenden Attribut dekorieren:

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

Grundsätzlich sagen Sie dem Compiler, dass Sie wissen, was Sie tun, und dass er sich keine Gedanken über mögliche Fehler machen muss.

Der wichtige Teil dieses Codes ist der zweite Parameter. Der Teil "CS4014:" unterdrückt die Warnung. Sie können alles schreiben, was Sie wollen.

Fábio
quelle
Funktioniert bei mir nicht: Visual Studio für Mac 7.0.1 (Build 24). Scheint, als sollte es aber - nein.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]unterdrückt die Nachricht im Fenster Fehlerliste, aber das Ausgabefenster zeigt immer noch eine Warnzeile
David Ching
35

Meine zwei Möglichkeiten, damit umzugehen.

Speichern Sie es in einer Discard-Variablen (C # 7).

Beispiel

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

Seit der Einführung von Rückwürfen in C # 7 halte ich dies nun für besser als die Unterdrückung der Warnung. Weil es nicht nur die Warnung unterdrückt, sondern auch die Feuer-und-Vergessen-Absicht klar macht.

Darüber hinaus kann der Compiler es im Release-Modus optimieren.

Unterdrücke es einfach

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

ist eine nette Lösung, um "zu feuern und zu vergessen".

Der Grund, warum diese Warnung vorhanden ist, liegt darin, dass Sie in vielen Fällen nicht beabsichtigen, eine Methode zu verwenden, die eine Aufgabe zurückgibt, ohne darauf zu warten. Es ist sinnvoll, die Warnung zu unterdrücken, wenn Sie feuern und vergessen möchten.

Wenn Sie Probleme haben, sich daran zu erinnern, wie man buchstabiert #pragma warning disable 4014 , lassen Sie es einfach von Visual Studio hinzufügen. Drücken Sie Strg +. um "Schnellaktionen" und dann "CS2014 unterdrücken" zu öffnen

Alles in allem

Es ist dumm, eine Methode zu erstellen, deren Ausführung einige weitere Ticks erfordert, nur um eine Warnung zu unterdrücken.

fjch1997
quelle
Dies funktionierte in Visual Studio für Mac 7.0.1 (Build 24).
IronRod
1
Es ist dumm, eine Methode zu erstellen, deren Ausführung einige weitere Ticks erfordert , nur um eine Warnung zu unterdrücken - diese fügt überhaupt keine zusätzlichen Ticks hinzu und IMO ist besser lesbar:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Viele Male, wenn ich AggressiveInliningden Compiler benutze, ignoriert er es einfach aus irgendeinem Grund
fjch1997
1
Ich mag die Pragma-Option, weil sie sehr einfach ist und nur für die aktuelle Codezeile (oder den aktuellen Codeabschnitt) gilt, nicht für eine ganze Methode.
Wasatchwizard
2
Vergessen Sie nicht, den Fehlercode wie zu verwenden #pragma warning disable 4014und anschließend die Warnung mit wiederherzustellen #pragma warning restore 4014. Es funktioniert immer noch ohne den Fehlercode, aber wenn Sie die Fehlernummer nicht hinzufügen, werden alle Meldungen unterdrückt.
DunningKrugerEffect
11

Eine einfache Möglichkeit, die Warnung zu stoppen, besteht darin, die Aufgabe beim Aufrufen einfach zuzuweisen:

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

Und so würden Sie in Ihrem ursprünglichen Beitrag Folgendes tun:

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
quelle
Ich habe diesen Ansatz in der Frage selbst erwähnt, als einen von denen, die ich nicht besonders mochte.
Noseratio
Hoppla! Ich habe das nicht bemerkt, weil es sich im selben Codeabschnitt wie Ihr Pragma befand ... Und ich suchte nach Antworten. Abgesehen davon, was gefällt Ihnen an dieser Methode nicht?
Noelicus
1
Ich mag es nicht, dass es taskwie eine vergessene lokale Variable aussieht. Fast so, wie der Compiler mir eine weitere Warnung geben sollte, wird so etwas wie " taskzugewiesen, aber sein Wert wird nie verwendet", außerdem nicht. Außerdem ist der Code dadurch weniger lesbar. Ich selbst benutze diesen Ansatz.
Noseratio
Fair genug - ich hatte ein ähnliches Gefühl, weshalb ich es fireAndForgetnenne ... also erwarte ich, dass es von nun an nicht mehr referenziert wird.
Noelicus
4

Der Grund für die Warnung ist, dass WorkAsync eine zurückgibt Task, die niemals gelesen oder erwartet wird. Sie können den Rückgabetyp von WorkAsync auf festlegenvoid und die Warnung wird .

In der Regel gibt eine Methode a zurück, Taskwenn der Anrufer den Status des Arbeiters kennen muss. Im Falle eines Fire-and-Forget sollte void zurückgegeben werden, um zu ähneln, dass der Aufrufer von der aufgerufenen Methode unabhängig ist.

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); 
}
Sieger
quelle
2

Warum nicht in eine asynchrone Methode einbinden, die void zurückgibt? Ein bisschen langwierig, aber alle Variablen werden verwendet.

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

Ich habe diesen Ansatz heute zufällig gefunden. Sie können einen Delegaten definieren und dem Delegaten zuerst die asynchrone Methode zuweisen.

    delegate Task IntermediateHandler();



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

und nenne es so

(new IntermediateHandler(AsyncOperation))();

...

Ich fand es interessant, dass der Compiler bei Verwendung des Delegaten nicht genau dieselbe Warnung ausgibt.

David Beavon
quelle
Sie müssen keinen Delegaten deklarieren, Sie können dies auch tun, (new Func<Task>(AsyncOperation))()obwohl IMO immer noch etwas zu ausführlich ist.
Noseratio