Wie kann ich warten, bis die asynchrone Methode abgeschlossen ist?

138

Ich schreibe eine WinForms-Anwendung, die Daten auf ein Gerät der USB-HID-Klasse überträgt. Meine Anwendung verwendet die hervorragende generische HID-Bibliothek v6.0, die hier zu finden ist . Kurz gesagt, wenn ich Daten auf das Gerät schreiben muss, wird folgender Code aufgerufen:

private async void RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    }
}

Wenn mein Code aus der while-Schleife herausfällt, muss ich einige Daten vom Gerät lesen. Das Gerät kann jedoch nicht sofort antworten, daher muss ich warten, bis dieser Anruf zurückkommt, bevor ich fortfahre. RequestToGetInputReport () wird wie folgt deklariert:

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

Für das, was es wert ist, sieht die Deklaration für GetInputReportViaInterruptTransfer () folgendermaßen aus:

internal async Task<int> GetInputReportViaInterruptTransfer()

Leider bin ich mit der Funktionsweise der neuen Async / Wait-Technologien in .NET 4.5 nicht sehr vertraut. Ich habe vorhin ein wenig über das Schlüsselwort await gelesen und dadurch den Eindruck erweckt, dass der Aufruf von GetInputReportViaInterruptTransfer () in RequestToGetInputReport () warten würde (und vielleicht auch?), Aber es scheint nicht so, als würde RequestToGetInputReport () aufgerufen selbst wartet, weil ich fast sofort wieder in die while-Schleife eintrete?

Kann jemand das Verhalten klären, das ich sehe?

bmt22033
quelle

Antworten:

131

Vermeiden Sie async void. Lassen Sie Ihre Methoden Taskstatt zurückgeben void. Dann kannst du awaitsie.

So was:

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    }
}

private async Task RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}
Stephen Cleary
quelle
1
Sehr Schön. Danke. Ich kratzte mir bei einem ähnlichen Thema am Kopf und der Unterschied bestand darin, voidzu dem zu wechseln , Taskwas Sie gesagt hatten.
Jeremy
8
Es ist eine Kleinigkeit, aber um der Konvention zu folgen, sollte bei beiden Methoden Async zu ihren Namen hinzugefügt werden, z. B. RequestToGetInputReportAsync ()
tymtam
6
und was ist, wenn der Anrufer die Hauptfunktion ist?
Symbiont
14
@symbiont: Dann verwenden SieGetAwaiter().GetResult()
Stephen Cleary
4
@AhmedSalah Das Taskstellt die Ausführung der Methode dar - also werden returnWerte Task.Resultund Ausnahmen gesetzt Task.Exception. Mit voidkann der Compiler keine Ausnahmen platzieren, sodass sie nur in einem Thread-Pool-Thread erneut ausgelöst werden.
Stephen Cleary
229

Die wichtigste Sache zu wissen asyncund awaitist , dass await nicht der Fall ist für den damit verbundenen Aufruf vollständig zu warten. Sie awaitmüssen das Ergebnis der Operation sofort und synchron zurückgeben, wenn die Operation bereits abgeschlossen ist, oder, falls dies nicht der Fall ist, eine Fortsetzung planen, um den Rest der asyncMethode auszuführen , und dann die Steuerung an den Aufrufer zurückgeben. Wenn der asynchrone Vorgang abgeschlossen ist, wird der geplante Abschluss ausgeführt.

Die Antwort auf die spezifische Frage im Titel Ihrer Frage besteht darin async, den Rückgabewert einer Methode (der vom Typ Taskoder sein sollte Task<T>) durch Aufrufen einer geeigneten WaitMethode zu blockieren :

public static async Task<Foo> GetFooAsync()
{
    // Start asynchronous operation(s) and return associated task.
    ...
}

public static Foo CallGetFooAsyncAndWaitOnResult()
{
    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;
}

In diesem Code-Snippet CallGetFooAsyncAndWaitOnResulthandelt es sich um einen synchronen Wrapper um eine asynchrone Methode GetFooAsync. Dieses Muster ist jedoch größtenteils zu vermeiden, da es einen gesamten Thread-Pool-Thread für die Dauer der asynchronen Operation blockiert. Dies ist eine ineffiziente Verwendung der verschiedenen asynchronen Mechanismen, die von APIs bereitgestellt werden, die große Anstrengungen unternehmen, um sie bereitzustellen.

Die Antwort unter "Warten" wartet nicht auf den Abschluss des Anrufs. Sie enthält mehrere detailliertere Erklärungen zu diesen Schlüsselwörtern.

In der Zwischenzeit async voidhält @Stephen Clearys Anleitung zu . Weitere nette Erklärungen dafür finden Sie unter http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ und https://jaylee.org/archive/ 2012/07/08 / c-scharf-asynchrone-Tipps-und-Tricks-Teil-2-asynchrone-void.html

Richard Cook
quelle
18
Ich finde es nützlich, über awaitein "asynchrones Warten" nachzudenken (und darüber zu sprechen) - das heißt, es blockiert die Methode (falls erforderlich), aber nicht den Thread . Es ist also sinnvoll, über RequestToSendOutputReport"Warten auf" zu sprechen RequestToGetInputReport, obwohl es kein blockierendes Warten ist.
Stephen Cleary
@ Richard Cook - vielen Dank für die zusätzliche Erklärung!
bmt22033
10
Dies sollte die akzeptierte Antwort sein, da sie die eigentliche Frage klarer beantwortet (dh wie man eine asynchrone Methode threadweise blockiert).
CSvan
Die beste Lösung ist, asynchron zu warten, bis die Aufgabe abgeschlossen ist. var result = Task.Run (async () => {return await yourMethod ();}). Result;
Ram Chittala
70

Die beste Lösung, um AsynMethod zu warten, bis die Aufgabe abgeschlossen ist, ist

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram Chittala
quelle
15
Oder dies für Ihre asynchrone "Leere": Task.Run (async () => {warte auf deine AsyncMethod ();}). Wait ();
Jiří Herník
1
Was ist der Vorteil davon gegenüber yourAsyncMethod (). Ergebnis?
Justin J Stark
1
Der einfache Zugriff auf die .Result-Eigenschaft wartet nicht wirklich, bis die Ausführung der Aufgabe abgeschlossen ist. Tatsächlich glaube ich, dass es eine Ausnahme auslöst, wenn es aufgerufen wird, bevor eine Aufgabe abgeschlossen ist. Ich denke, der Vorteil des Umhüllens eines Task.Run () -Aufrufs besteht darin, dass "wait", wie Richard Cook unten erwähnt, nicht auf den Abschluss einer Aufgabe wartet, sondern die Verwendung eines .Wait () -Aufrufs Ihren gesamten Thread-Pool blockiert . Auf diese Weise können Sie (synchron) eine asynchrone Methode in einem separaten Thread ausführen. Etwas verwirrend, aber da ist es.
Lucas Leblanc
schön das Ergebnis dort zu werfen, genau das, was ich brauchte
Gerry
Schnelle Erinnerung ECMA7 featurelike assync () oder warten nicht in der Umgebung vor ECMA7 funktionieren.
Mbotet
0

Hier ist eine Problemumgehung mit einem Flag:

//outside your event or method, but inside your class
private bool IsExecuted = false;

private async Task MethodA()
{

//Do Stuff Here

IsExecuted = true;
}

.
.
.

//Inside your event or method

{
await MethodA();

while (!isExecuted) Thread.Sleep(200); // <-------

await MethodB();
}
schockierende Lemi
quelle
-1

Setzen Sie einfach Wait (), um zu warten, bis die Aufgabe abgeschlossen ist

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
quelle
Dies blockiert den aktuellen Thread. Das ist normalerweise eine schlechte Sache.
Pure.Krome
-4

Eigentlich fand ich dies hilfreicher für Funktionen, die IAsyncAction zurückgeben.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Barış Tanyeri
quelle
-5

Das folgende Snippet zeigt eine Möglichkeit, um sicherzustellen, dass die erwartete Methode abgeschlossen ist, bevor Sie zum Aufrufer zurückkehren. Ich würde jedoch nicht sagen, dass es eine gute Praxis ist. Bitte bearbeiten Sie meine Antwort mit Erklärungen, wenn Sie anders denken.

public async Task AnAsyncMethodThatCompletes()
{
    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() => { }); // <-- This line here, at the end
}

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
quelle
Downvoters, möchten Sie das erklären, wie ich in der Antwort gefragt habe? Weil das soweit ich weiß gut funktioniert ...
Jerther
3
Das Problem ist, dass der einzige Weg, das await+ das zu tun, Console.WriteLinedarin besteht, ein zu werden Task, was die Kontrolle zwischen den beiden aufgibt. Ihre "Lösung" ergibt also letztendlich eine Task<T>, die das Problem nicht angeht. Wenn Sie ein Task.WaitTestament ausführen , wird die Verarbeitung tatsächlich gestoppt (mit Deadlock-Möglichkeiten usw.). Mit anderen Worten, awaitwartet nicht wirklich, es kombiniert einfach zwei asynchron ausführbare Teile zu einem einzigen Task(auf den jemand schauen oder warten kann)
Ruben Bartelink