Wenn dieser Aufruf nicht erwartet wird, wird die Ausführung der aktuellen Methode fortgesetzt


135

Ich habe gerade VS2012 bekommen und versucht, es in den Griff zu bekommen async.

Angenommen, ich habe eine Methode, die einen Wert von einer blockierenden Quelle abruft. Ich möchte nicht, dass der Aufrufer der Methode blockiert. Ich könnte die Methode schreiben, um einen Rückruf anzunehmen, der beim Eintreffen des Werts aufgerufen wird. Da ich jedoch C # 5 verwende, entscheide ich mich, die Methode asynchron zu machen, damit Anrufer sich nicht mit Rückrufen befassen müssen:

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

Hier ist eine Beispielmethode, die es aufruft. Wenn dies PromptForStringAsyncnicht asynchron wäre, müsste für diese Methode ein Rückruf in einen Rückruf verschachtelt werden. Mit Async kann ich meine Methode auf ganz natürliche Weise schreiben:

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

So weit, ist es gut. Das Problem ist , wenn ich anrufen GetNameAsync:

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

Der springende Punkt GetNameAsyncist, dass es asynchron ist. Ich möchte nicht, dass es blockiert wird, da ich so schnell wie möglich zu MainWorkOfApplicationIDontWantBlocked zurückkehren und GetNameAsync im Hintergrund ausführen lassen möchte. Wenn ich es jedoch so nenne, erhalte ich eine Compiler-Warnung in der GetNameAsyncZeile:

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.

Mir ist völlig bewusst, dass "die Ausführung der aktuellen Methode fortgesetzt wird, bevor der Aufruf abgeschlossen ist". Das ist der Punkt des asynchronen Codes, richtig?

Ich bevorzuge es, meinen Code ohne Warnungen zu kompilieren, aber hier gibt es nichts zu "reparieren", da der Code genau das tut, was ich beabsichtige. Ich kann die Warnung entfernen, indem ich den Rückgabewert von GetNameAsync:

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

Aber jetzt habe ich überflüssigen Code. Visual Studio scheint zu verstehen, dass ich gezwungen war, diesen unnötigen Code zu schreiben, da er die normale Warnung "Wert nie verwendet" unterdrückt.

Ich kann die Warnung auch entfernen, indem ich GetNameAsync in eine Methode einbinde, die nicht asynchron ist:

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

Aber das ist noch mehr überflüssig Code. Ich muss also Code schreiben, den ich nicht brauche oder eine unnötige Warnung toleriere.

Gibt es etwas an meiner Verwendung von Async, das hier nicht stimmt?


2
Übrigens, bei der Implementierung PromptForStringAsyncerledigen Sie mehr Arbeit als nötig. Geben Sie einfach das Ergebnis von zurück Task.Factory.StartNew. Es ist bereits eine Aufgabe, deren Wert die in die Konsole eingegebene Zeichenfolge ist. Es besteht keine Notwendigkeit, darauf zu warten und das Ergebnis zurückzugeben. Dies bringt keinen neuen Wert.
Servy

Wwouldn't es mehr Sinn machen , für GetNameAsyncden vollständigen Namen zur Verfügung zu stellen, die vom Benutzer (dh zur Verfügung gestellt wurde Task<Name>, und nicht nur eine Rückkehr Task? DoStuffKönnte dann speichern , diese Aufgabe, und entweder awaites nach dem anderen Verfahren oder sogar passiert die Aufgabe zu , dass andere Methode, damit es könnte awaitoder Waites irgendwo innerhalb seiner Implementierung.
Servy

@Servy: Wenn ich nur die Aufgabe zurückgebe, erhalte ich die Fehlermeldung "Da es sich um eine asynchrone Methode handelt, muss der Rückgabeausdruck vom Typ 'string' und nicht vom Typ 'Task <string>' sein".
Schlamm

1
Entfernen Sie das asyncSchlüsselwort.
Servy

13
IMO, dies war eine schlechte Wahl für eine Warnung des C # -Teams. Warnungen sollten für Dinge sein, die mit ziemlicher Sicherheit falsch sind. Es gibt viele Fälle, in denen Sie eine asynchrone Methode "feuern und vergessen" möchten, und andere Fälle, in denen Sie tatsächlich darauf warten möchten.
MgSam

Antworten:


103

Wenn Sie das Ergebnis wirklich nicht benötigen, können Sie einfach die GetNameAsyncSignatur des Benutzers ändern , um Folgendes zurückzugeben void:

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

Überlegen Sie, ob Sie eine Antwort auf eine verwandte Frage erhalten möchten: Was ist der Unterschied zwischen der Rückgabe von void und der Rückgabe einer Aufgabe?

Aktualisieren

Wenn Sie das Ergebnis benötigen, können Sie das ändern GetNameAsync, um beispielsweise Folgendes zurückzugeben 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;
}

Und benutze es wie folgt:

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

15
Dies wäre nur dann der Fall, wenn er sich nie um das Ergebnis kümmert, anstatt das Ergebnis diesmal nur nicht zu benötigen .
Servy

3
@Servy, richtig, danke für die Klarstellung. Aber OPs GetNameAsyncgeben keinen Wert zurück (außer natürlich das Ergebnis selbst).
Nikolay Khil

2
Richtig, aber durch die Rückgabe einer Aufgabe kann er wissen, wann alle asynchronen Vorgänge ausgeführt wurden. Wenn es zurückkommt void, kann er nicht wissen, wann es fertig ist. Das habe ich gemeint, als ich in meinem vorherigen Kommentar "das Ergebnis" sagte.
Servy

29
In der Regel sollten Sie niemals eine async voidMethode haben, außer für Ereignishandler.
Daniel Mann

2
Eine andere Sache, die Sie hier beachten sollten, ist, dass das Verhalten bei nicht beobachteten Ausnahmen anders ist, wenn Sie zu async voideiner Ausnahme wechseln, die Sie nicht abfangen, was Ihren Prozess zum Absturz bringt, aber in .net 4.5 läuft es weiter.
Caleb Vear

62

Ich bin ziemlich spät bei dieser Diskussion, aber es gibt auch die Möglichkeit, die #pragmaVorprozessor-Direktive zu verwenden. Ich habe hier und da einen asynchronen Code, auf den ich unter bestimmten Bedingungen ausdrücklich nicht warten möchte, und ich mag Warnungen und nicht verwendete Variablen nicht wie der Rest von Ihnen:

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

Das "4014"kommt von dieser MSDN-Seite: Compiler-Warnung (Stufe 1) CS4014 .

Siehe auch die Warnung / Antwort von @ ryan-horath hier https://stackoverflow.com/a/12145047/928483 .

Ausnahmen, die während eines nicht erwarteten asynchronen Aufrufs ausgelöst werden, gehen verloren. Um diese Warnung zu entfernen, sollten Sie einer Variablen den Task-Rückgabewert des asynchronen Aufrufs zuweisen. Dies stellt sicher, dass Sie Zugriff auf alle ausgelösten Ausnahmen haben, die im Rückgabewert angegeben werden.

Update für C # 7.0

C # 7.0 fügt eine neue Funktion hinzu, Variablen verwerfen: Discards - C # Guide , die auch in dieser Hinsicht hilfreich sein kann.

_ = SomeMethodAsync();

5
Das ist absolut großartig. Ich hatte keine Ahnung, dass du das tun könntest. Danke dir!
Maxim Gershkovich

1
Es ist nicht einmal erforderlich zu sagen var, schreiben Sie einfach_ = SomeMethodAsync();
Ray

41

Ich mag die Lösungen nicht besonders, die entweder die Aufgabe einer nicht verwendeten Variablen zuweisen oder die Methodensignatur so ändern, dass sie void zurückgibt. Ersteres erstellt überflüssigen, nicht intuitiven Code, während letzteres möglicherweise nicht möglich ist, wenn Sie eine Schnittstelle implementieren oder die Funktion, in der Sie die zurückgegebene Aufgabe verwenden möchten, anderweitig verwenden.

Meine Lösung besteht darin, eine Erweiterungsmethode von Task namens DoNotAwait () zu erstellen, die nichts bewirkt. Dies unterdrückt nicht nur alle Warnungen, ReSharper oder andere, sondern macht den Code verständlicher und zeigt zukünftigen Verwaltern Ihres Codes an, dass Sie wirklich beabsichtigt haben, dass der Anruf nicht erwartet wird.

Verlängerungsmethode:

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

Verwendung:

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

Bearbeitet, um hinzuzufügen: Dies ähnelt der Lösung von Jonathan Allen, bei der die Erweiterungsmethode die Aufgabe starten würde, wenn sie nicht bereits gestartet wurde. Ich bevorzuge jedoch Einzweckfunktionen, damit die Absicht des Anrufers völlig klar ist.


1
Ich mag das, aber ich habe es in Unawait () umbenannt;)
Ostati

29

async void IST SCHLECHT!

  1. Was ist der Unterschied zwischen der Rückgabe von void und der Rückgabe einer Aufgabe?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Was ich vorschlage ist, dass Sie das explizit Tasküber eine anonyme Methode ausführen ...

z.B

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

Oder wenn Sie wollten, dass es blockiert, können Sie auf die anonyme Methode warten

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

Wenn Ihre GetNameAsyncMethode jedoch mit der Benutzeroberfläche oder einer anderen Benutzeroberfläche interagieren muss (WINRT / MVVM, ich sehe Sie an), wird sie etwas funkiger =)

Sie müssen den Verweis wie folgt an den UI-Dispatcher übergeben ...

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

Und dann müssen Sie in Ihrer asynchronen Methode mit Ihrer Benutzeroberfläche oder an Benutzeroberflächen gebundenen Elementen interagieren, die der Dispatcher für ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

Ihre Aufgabenerweiterung ist fantastisch. Ich weiß nicht, warum ich noch keinen implementiert habe.
Mikael Dúi Bolinder

8
Der erste Ansatz, den Sie erwähnen, führt zu einer anderen Warnung: 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. Dadurch wird auch ein neuer Thread erstellt, während ein neuer Thread nicht unbedingt nur mit async / await erstellt wird.
Gregsdennis

Sie sollten auf sein, Sir!
Ozkan

16

Das mache ich gerade:

SomeAyncFunction().RunConcurrently();

Wo RunConcurrentlyist definiert als ...

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


1
+1. Dies scheint zu vermeiden, dass alle Probleme, die in den Kommentaren anderer Antworten verglichen werden, vermieden werden. Vielen Dank.
Grault

3
Sie brauchen nur public static void Forget(this Task task) { }
Folgendes

1
Was meinst du dort @ShitalShah
Jay Wick

@ShitalShah, das nur mit automatisch startenden Aufgaben funktioniert, wie sie mit erstellt wurden async Task. Einige Aufgaben müssen manuell gestartet werden.
Jonathan Allen

7

Gemäß dem Microsoft-Artikel zu dieser Warnung können Sie sie lösen, indem Sie die zurückgegebene Aufgabe einfach einer Variablen zuweisen. Unten finden Sie eine Übersetzung des im Microsoft-Beispiel bereitgestellten Codes:

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

Beachten Sie, dass dies in ReSharper zur Meldung "Lokale Variable wird nie verwendet" führt.


Ja, das unterdrückt die Warnung, aber nein, das löst nichts wirklich. Task-rückgabefunktionen sollten await-ed sein, es sei denn, Sie haben einen sehr guten Grund, dies nicht zu tun . Es gibt hier keinen Grund, warum das Verwerfen der Aufgabe besser wäre als die bereits akzeptierte Antwort auf die Verwendung einer async voidMethode.

Ich bevorzuge auch die Void-Methode. Dies macht StackOverflow intelligenter als Microsoft, aber das sollte natürlich selbstverständlich sein.
Devlord

4
async voidführt zu schwerwiegenden Problemen bei der Fehlerbehandlung und führt zu nicht testbarem Code (siehe meinen MSDN-Artikel ). Es wäre weitaus besser, die Variable zu verwenden - wenn Sie absolut sicher sind , dass Ausnahmen stillschweigend verschluckt werden sollen. Wahrscheinlicher wäre es, dass die Operation zwei TaskSekunden beginnen und dann eine machen möchte await Task.WhenAll.
Stephen Cleary

@StephenCleary Ob Ausnahmen von nicht beobachteten Aufgaben ignoriert werden, ist konfigurierbar. Wenn die Möglichkeit besteht, dass Ihr Code in einem anderen Projekt verwendet wird, lassen Sie dies nicht zu. Eine einfache async void DoNotWait(Task t) { await t; }Hilfsmethode kann verwendet werden, um die Nachteile der von async voidIhnen beschriebenen Methoden zu vermeiden . (Und ich denke nicht, Task.WhenAllwas das OP will, aber es könnte sehr gut sein.)

@hvd: Ihre Hilfsmethode würde es testbar machen, aber es würde immer noch eine sehr schlechte Unterstützung für die Ausnahmebehandlung geben.
Stephen Cleary

3

Hier eine einfache Lösung.

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

Grüße


1

Es ist Ihr vereinfachtes Beispiel, das den überflüssigen Code verursacht. Normalerweise möchten Sie die Daten verwenden, die zu einem bestimmten Zeitpunkt im Programm von der blockierenden Quelle abgerufen wurden. Sie möchten also das Ergebnis zurück, damit Sie auf die Daten zugreifen können.

Wenn Sie wirklich etwas haben, das völlig isoliert vom Rest des Programms passiert, wäre Async nicht der richtige Ansatz. Starten Sie einfach einen neuen Thread für diese Aufgabe.


Ich tun will , dass die Daten aus der blockierenden Quelle geholt verwenden, aber ich mag nicht , um den Anrufer zu blockieren , während ich auf ihn warten. Ohne Async können Sie dies erreichen, indem Sie einen Rückruf übergeben. In meinem Beispiel habe ich zwei asynchrone Methoden, die nacheinander aufgerufen werden müssen, und der zweite Aufruf muss einen vom ersten zurückgegebenen Wert verwenden. Dies würde bedeuten, Callback-Handler zu verschachteln, was höllisch hässlich wird. Die Dokumentation, die ich gelesen habe, legt nahe, dass dies speziell das ist, was async( zum Beispiel ) aufgeräumt werden soll
Schlamm

@Mud: Senden Sie einfach das Ergebnis vom ersten asynchronen Aufruf als Parameter an den zweiten Aufruf. Auf diese Weise startet der Code in der zweiten Methode sofort und kann auf das Ergebnis des ersten Aufrufs warten, wenn es ihm gefällt.
Guffa

Ich kann das tun, aber ohne Async bedeutet das verschachtelte Rückrufe wie folgt: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Selbst in diesem trivialen Beispiel ist es eine Hündin zu analysieren. Mit Async wird beim Schreiben result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); ein äquivalenter Code für mich generiert, der viel einfacher zu lesen ist (obwohl beide in diesem Kommentar nicht sehr gut lesbar sind!)
Mud

@ Schlamm: Ja. Sie können die zweite asynchrone Methode jedoch direkt nach der ersten aufrufen Use. Es gibt keinen Grund, auf den synchronen Aufruf von zu warten .
Guffa

Ich verstehe wirklich nicht, was du sagst. MethodWithCallback ist asynchron. Wenn ich sie hintereinander anrufe, ohne auf den ersten Anruf zu warten, wird der zweite Anruf möglicherweise vor dem ersten beendet. Ich benötige jedoch das Ergebnis des ersten Aufrufs im Handler für den zweiten. Also muss ich beim ersten Anruf warten.
Schlamm

0

Möchten Sie das Ergebnis wirklich ignorieren? wie beim Einschließen unerwarteter Ausnahmen?

Wenn nicht, möchten Sie vielleicht einen Blick auf diese Frage werfen: Fire and Forget-Ansatz ,


0

Wenn Sie die Methodensignatur nicht ändern möchten, um sie zurückzugeben void(da die Rückgabe voidimmer ungültig sein sollte ), können Sie die C # 7.0+ Discard-Funktion wie diese verwenden, die etwas besser ist als die Zuweisung zu einer Variablen (und die meisten anderen entfernen sollte) Warnungen zu Quellenvalidierungswerkzeugen):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.