So rufen Sie eine asynchrone Methode in C # sicher auf, ohne zu warten

320

Ich habe eine asyncMethode, die keine Daten zurückgibt:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

Ich rufe dies von einer anderen Methode auf, die einige Daten zurückgibt:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

Wenn Sie MyAsyncMethod()ohne Wartezeit anrufen, wird in Visual Studio die Warnung " Da dieser Anruf nicht erwartet wird, wird die aktuelle Methode weiter ausgeführt, bevor der Anruf abgeschlossen ist " angezeigt. Auf der Seite für diese Warnung heißt es:

Sie sollten die Warnung nur unterdrücken, wenn Sie sicher sind, dass Sie nicht auf den Abschluss des asynchronen Aufrufs warten möchten und die aufgerufene Methode keine Ausnahmen auslöst .

Ich bin sicher, ich möchte nicht warten, bis der Anruf abgeschlossen ist. Ich brauche oder habe keine Zeit dafür. Der Anruf kann jedoch Ausnahmen auslösen.

Ich bin ein paar Mal auf dieses Problem gestoßen und bin mir sicher, dass es ein häufiges Problem ist, das eine gemeinsame Lösung haben muss.

Wie rufe ich eine asynchrone Methode sicher auf, ohne auf das Ergebnis zu warten?

Aktualisieren:

Für Leute, die vorschlagen, dass ich nur auf das Ergebnis warte, ist dies Code, der auf eine Webanforderung in unserem Webdienst (ASP.NET-Web-API) reagiert. Das Warten in einem UI-Kontext hält den UI-Thread frei, aber das Warten in einem Webanforderungsaufruf wartet, bis die Aufgabe beendet ist, bevor auf die Anfrage geantwortet wird, wodurch die Antwortzeiten ohne Grund verlängert werden.

George Powell
quelle
Warum nicht einfach eine Abschlussmethode erstellen und dort einfach ignorieren? Denn wenn es auf Hintergrund-Thread läuft. Dann wird Ihr Programm ohnehin nicht daran gehindert, zu beenden.
Yahya
1
Wenn Sie nicht auf das Ergebnis warten möchten, können Sie die Warnung nur ignorieren / unterdrücken. Wenn Sie es für das Ergebnis / Ausnahme warten soll dannMyAsyncMethod().Wait()
Peter Ritchie
1
Über Ihre Bearbeitung: Das ergibt für mich keinen Sinn. Angenommen, die Antwort wird 1 Sekunde nach der Anforderung an den Client gesendet, und 2 Sekunden später löst Ihre asynchrone Methode eine Ausnahme aus. Was würden Sie mit dieser Ausnahme machen? Sie können es nicht an den Client senden, wenn Ihre Antwort bereits gesendet wurde. Was würden Sie sonst noch damit machen?
2
@ Romoku Fair genug. Angenommen, jemand schaut sich das Protokoll an. :)
2
Eine Variation des ASP.NET-Web-API-Szenarios ist eine selbst gehostete Web-API in einem langlebigen Prozess (z. B. einem Windows-Dienst), bei dem eine Anforderung eine langwierige Hintergrundaufgabe erstellt, um etwas Teuereres zu tun, dies aber dennoch tun möchte Erhalten Sie schnell eine Antwort mit einem HTTP 202 (Akzeptiert).
David Rubin

Antworten:

185

Wenn Sie die Ausnahme "asynchron" erhalten möchten, können Sie Folgendes tun:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Auf diese Weise können Sie eine Ausnahme in einem anderen Thread als dem "Haupt" -Thread behandeln. Dies bedeutet, dass Sie nicht auf den Aufruf des aufrufenden MyAsyncMethod()Threads "warten" müssen MyAsyncMethod. Sie können jedoch weiterhin etwas mit einer Ausnahme tun - jedoch nur, wenn eine Ausnahme auftritt.

Aktualisieren:

technisch könnte man etwas ähnliches machen mit await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... was nützlich wäre, wenn Sie try/ catch(oder using) speziell verwenden müssten, aber ich finde das ContinueWithetwas expliziter, weil Sie wissen müssen, was ConfigureAwait(false)bedeutet.

Peter Ritchie
quelle
7
Ich habe daraus eine Erweiterungsmethode gemacht für Task: public static class AsyncUtility {public static void PerformAsyncTaskWithoutAwait (diese Task-Task, Aktion <Task> exceptionHandler) {var dummy = task.ContinueWith (t => exceptionHandler (t), TaskContinuationOptions.OnlyOnFaulted); }} Verwendung: MyAsyncMethod (). PerformAsyncTaskWithoutAwait (t => log.ErrorFormat ("Beim Aufrufen von MyAsyncMethod ist ein Fehler aufgetreten: \ n {0}", t.Exception));
Mark Avenius
2
Downvoter. Bemerkungen? Wenn etwas in der Antwort nicht stimmt, würde ich es gerne wissen und / oder beheben.
Peter Ritchie
2
Hey - ich habe nicht abgelehnt, aber ... Kannst du dein Update erklären? Der Anruf sollte nicht erwartet werden. Wenn Sie es also so machen, dann warten Sie auf den Anruf, Sie setzen den erfassten Kontext einfach nicht fort ...
Bartosz
1
"warten" ist in diesem Zusammenhang nicht die richtige Beschreibung. Die Zeile nach dem ConfiguratAwait(false)wird erst ausgeführt, wenn die Aufgabe abgeschlossen ist, aber der aktuelle Thread "wartet" nicht (dh blockiert) darauf. Die nächste Zeile wird asynchron zum Aufruf des Wartens aufgerufen. Ohne das würde ConfigureAwait(false)die nächste Zeile im ursprünglichen Webanforderungskontext ausgeführt. Mit dem ConfigurateAwait(false)ist es in diesem Zusammenhang , wie der Asynchron - Methode (Task) ausgeführt wird , den ursprünglichen Kontext / Thread befreit auf , um fortzufahren ...
Peter Ritchie
15
Die ContinueWith-Version ist nicht mit der try {await} catch {} -Version identisch. In der ersten Version wird alles nach ContinueWith () sofort ausgeführt. Die erste Aufgabe wird ausgelöst und vergessen. In der zweiten Version wird alles nach dem catch {} erst ausgeführt, nachdem die erste Aufgabe abgeschlossen wurde. Die zweite Version entspricht "warte auf MyAsyncMethod (). ContinueWith (t => Console.WriteLine (t.Exception), TaskContinuationOptions.OnlyOnFaulted) .ConfigureAwait (fals);
Thanasis Ioannidis
66

Sie sollten zuerst überlegen, GetStringDataeine asyncMethode zu erstellen und awaitdie Aufgabe von ihr zurückgeben zu lassen MyAsyncMethod.

Wenn Sie absolut sicher sind, dass Sie keine Ausnahmen behandeln müssen MyAsyncMethod oder wissen, wann sie abgeschlossen sind, können Sie Folgendes tun:

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

Übrigens ist dies kein "häufiges Problem". Es ist sehr selten, dass Sie Code ausführen möchten und sich nicht darum kümmern möchten, ob er abgeschlossen wird, und nicht darum, ob er erfolgreich abgeschlossen wird.

Aktualisieren:

Da Sie auf ASP.NET sind und frühzeitig zurückkehren möchten, ist mein Blog-Beitrag zu diesem Thema möglicherweise hilfreich . ASP.NET wurde jedoch nicht dafür entwickelt, und es gibt keine Garantie dafür, dass Ihr Code ausgeführt wird, nachdem die Antwort zurückgegeben wurde. ASP.NET wird sein Bestes tun, um es laufen zu lassen, kann es jedoch nicht garantieren.

Dies ist also eine gute Lösung für etwas Einfaches wie das Werfen eines Ereignisses in ein Protokoll, in dem es nicht wirklich wichtig ist, ob Sie hier und da ein paar verlieren. Es ist keine gute Lösung für geschäftskritische Vorgänge. In diesen Situationen Sie müssen eine komplexere Architektur verabschieden, mit persistent die Operationen (zB Azure Queues, MSMQ) und einem separaten Hintergrundprozess (zB Azure Worker Role, Win32 - Dienst) , um sie zu verarbeiten , zu speichern.

Stephen Cleary
quelle
2
Ich denke, Sie könnten falsch verstanden haben. Es ist mir egal, ob es Ausnahmen auslöst und fehlschlägt, aber ich möchte nicht auf die Methode warten müssen, bevor ich meine Daten zurückgebe. Sehen Sie sich auch meine Bearbeitung des Kontexts an, in dem ich arbeite, wenn dies einen Unterschied macht.
George Powell
5
@ GeorgePowell: Es ist sehr gefährlich, Code in einem ASP.NET-Kontext ohne aktive Anforderung auszuführen. Ich habe einen Blog-Beitrag, der Ihnen vielleicht helfen kann , aber ohne mehr über Ihr Problem zu wissen, kann ich nicht sagen, ob ich diesen Ansatz empfehlen würde oder nicht.
Stephen Cleary
@ StephenCleary Ich habe ein ähnliches Bedürfnis. In meinem Beispiel habe / brauche ich eine Stapelverarbeitungs-Engine, um in der Cloud ausgeführt zu werden. Ich werde den Endpunkt "pingen", um die Stapelverarbeitung zu starten, aber ich möchte sofort zurückkehren. Da das Pingen gestartet wird, kann es von dort aus alles verarbeiten. Wenn es Ausnahmen gibt, die ausgelöst werden, werden sie einfach in meinen "BatchProcessLog / Error" -Tabellen protokolliert ...
ganders
8
In C # 7 können Sie ersetzen var _ = MyAsyncMethod();mit _ = MyAsyncMethod();. Dadurch wird die Warnung CS4014 weiterhin vermieden, es wird jedoch etwas deutlicher, dass Sie die Variable nicht verwenden.
Brian
48

Die Antwort von Peter Ritchie war genau das, was ich wollte, und Stephen Clearys Artikel über die frühzeitige Rückkehr in ASP.NET war sehr hilfreich.

Als allgemeineres Problem (nicht spezifisch für einen ASP.NET-Kontext) demonstriert die folgende Konsolenanwendung die Verwendung und das Verhalten von Peters Antwort mit Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData()Rückgabe frühzeitig ohne Wartezeit MyAsyncMethod()und eingeworfene Ausnahmen MyAsyncMethod()werden in OnMyAsyncMethodFailed(Task task)und nicht in try/ catchum behandeltGetStringData()

George Powell
quelle
9
Entfernen Sie Console.ReadLine();und fügen Sie ein wenig Schlaf / Verzögerung hinzu, MyAsyncMethodund Sie werden nie die Ausnahme sehen.
Tymtam
17

Am Ende habe ich diese Lösung:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}
Filimindji
quelle
7

Dies nennt man Feuer und Vergessen, und dafür gibt es eine Erweiterung .

Verbraucht eine Aufgabe und macht nichts damit. Nützlich für Fire-and-Forget-Aufrufe von asynchronen Methoden innerhalb von asynchronen Methoden.

Installieren Sie das Nuget-Paket .

Verwenden:

MyAsyncMethod().Forget();
verschwenden
quelle
11
Es macht nichts, es ist nur ein Trick, um die Warnung zu entfernen. Siehe github.com/microsoft/vs-threading/blob/master/src/…
Paolo Fulgoni
2

Ich denke, es stellt sich die Frage, warum Sie das tun müssen. Der Grund für asyncin C # 5.0 ist, dass Sie auf ein Ergebnis warten können. Diese Methode ist eigentlich nicht asynchron, sondern wird jeweils nur aufgerufen, um den aktuellen Thread nicht zu stark zu stören.

Vielleicht ist es besser, einen Thread zu starten und ihn alleine zu beenden.

Rhughes
quelle
2
asyncist ein bisschen mehr als nur ein Ergebnis "abzuwarten". "Warten" bedeutet, dass die Zeilen nach "Warten" asynchron auf demselben Thread ausgeführt werden, der "Warten" aufgerufen hat. Dies kann natürlich ohne "Warten" geschehen, aber am Ende haben Sie eine Reihe von Delegierten und verlieren das sequenzielle Erscheinungsbild des Codes (sowie die Fähigkeit zu verwenden usingund try/catch...
Peter Ritchie
1
@PeterRitchie Sie können eine Methode haben, die funktional asynchron ist, das awaitSchlüsselwort nicht verwendet und das Schlüsselwort nicht verwendet async, aber es macht überhaupt keinen Sinn, das asyncSchlüsselwort zu verwenden, ohne es auch awaitin der Definition dieser Methode zu verwenden.
Servy
1
@PeterRitchie Aus der Aussage: " asyncist ein bisschen mehr als nur" auf "ein Ergebnis zu warten". Das asyncSchlüsselwort (Sie haben impliziert, dass es das Schlüsselwort ist, indem Sie es in Backticks einschließen) bedeutet nichts weiter als auf das Ergebnis zu warten. Es ist Asynchronität, wie die allgemeinen CS-Konzepte, die mehr bedeutet, als nur auf ein Ergebnis zu warten.
Servy
1
@Servy asyncerstellt eine Zustandsmaschine, die alle Wartezeiten innerhalb der asynchronen Methode verwaltet. Wenn awaitdie Methode kein s enthält, wird diese Zustandsmaschine weiterhin erstellt, die Methode ist jedoch nicht asynchron. Und wenn die asyncMethode zurückkehrt void, gibt es nichts zu erwarten. Es ist also mehr als nur ein Ergebnis abzuwarten.
Peter Ritchie
1
@PeterRitchie Solange die Methode eine Aufgabe zurückgibt, können Sie darauf warten. Die Zustandsmaschine oder das asyncSchlüsselwort sind nicht erforderlich . Alles, was Sie tun müssen (und alles, was am Ende mit einer Zustandsmaschine in diesem speziellen Fall wirklich passiert), ist, dass die Methode synchron ausgeführt und dann in eine abgeschlossene Aufgabe eingeschlossen wird. Ich nehme an, technisch gesehen entfernen Sie nicht nur die Zustandsmaschine. Sie entfernen die Zustandsmaschine und rufen dann auf Task.FromResult. Ich ging davon aus, dass Sie (und auch die Compiler-Autoren) das Addendum selbst hinzufügen könnten.
Servy
0

Bei Technologien mit Nachrichtenschleifen (nicht sicher, ob ASP einer von ihnen ist) können Sie die Schleife blockieren und Nachrichten verarbeiten, bis die Aufgabe beendet ist, und mit ContinueWith den Code entsperren:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Dieser Ansatz ähnelt dem Blockieren von ShowDialog und dem Anhalten der Benutzeroberfläche.

haimb
quelle
0

Ich bin zu spät zur Party hier, aber es gibt eine großartige Bibliothek, die ich benutzt habe und auf die ich in den anderen Antworten nicht verwiesen habe

https://github.com/brminnick/AsyncAwaitBestPractices

Wenn Sie "Fire And Forget" benötigen, rufen Sie die Erweiterungsmethode für die Aufgabe auf.

Durch das Übergeben der Aktion onException an den Aufruf wird sichergestellt, dass Sie das Beste aus beiden Welten erhalten - Sie müssen nicht auf die Ausführung warten und Ihre Benutzer verlangsamen, während die Möglichkeit erhalten bleibt, die Ausnahme auf elegante Weise zu behandeln.

In Ihrem Beispiel würden Sie es folgendermaßen verwenden:

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

Es gibt auch erwartete AsyncCommands, die ICommand sofort implementieren, was für meine MVVM Xamarin-Lösung großartig ist

Adam Diament
quelle
-1

Die Lösung besteht darin, den HttpClient in eine andere Ausführungsaufgabe ohne Sincronisierungskontext zu starten:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);
Ernesto Garcia
quelle
-1

Wenn Sie das wirklich wollen. Nur um "Async-Methode in C # ohne Wartezeit aufrufen" zu adressieren, können Sie die Async-Methode in a ausführen Task.Run. Dieser Ansatz wartet bis zum MyAsyncMethodEnde.

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

awaitEntpackt die ResultAufgabe asynchron , während nur die Verwendung von Ergebnis blockiert wird, bis die Aufgabe abgeschlossen ist.

CharithJ
quelle
-1

Normalerweise gibt die asynchrone Methode die Task-Klasse zurück. Wenn Sie eine Wait()Methode oder eine ResultEigenschaft verwenden und Code eine Ausnahme auslöst - der Ausnahmetyp wird eingeschlossen AggregateException-, müssen Sie eine Abfrage durchführenException.InnerException , um die richtige Ausnahme zu finden.

Es ist aber auch möglich zu verwenden .GetAwaiter().GetResult() stattdessen eine asynchrone Aufgabe zu warten, aber keine Ausnahme zu verpacken.

Hier ist ein kurzes Beispiel:

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

Möglicherweise möchten Sie auch in der Lage sein, einige Parameter von der asynchronen Funktion zurückzugeben. Action<return type>Dies kann erreicht werden, indem Sie beispielsweise zusätzliche Funktionen für die asynchrone Funktion bereitstellen :

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

Beachten Sie, dass asynchrone Methoden normalerweise Suffixnamen haben ASync, um Kollisionen zwischen Synchronisierungsfunktionen mit demselben Namen zu vermeiden. (ZB FileStream.ReadAsync) - Ich habe Funktionsnamen aktualisiert, um dieser Empfehlung zu folgen.

TarmoPikaro
quelle