Come attendere il completamento del metodo asincrono?


138

Sto scrivendo un'applicazione WinForms che trasferisce i dati su un dispositivo di classe USB HID. La mia applicazione utilizza l'eccellente libreria Generic HID v6.0 che può essere trovata qui . In breve, quando devo scrivere i dati sul dispositivo, questo è il codice che viene chiamato:

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

Quando il mio codice esce dal ciclo while, devo leggere alcuni dati dal dispositivo. Tuttavia, il dispositivo non è in grado di rispondere immediatamente, quindi devo attendere che questa chiamata torni prima di continuare. Come attualmente esiste, RequestToGetInputReport () è dichiarato in questo modo:

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

Per quello che vale, la dichiarazione per GetInputReportViaInterruptTransfer () è simile al seguente:

internal async Task<int> GetInputReportViaInterruptTransfer()

Sfortunatamente, non ho molta familiarità con il funzionamento delle nuove tecnologie asincrone / wait in .NET 4.5. Ho letto un po 'prima la parola chiave wait e mi ha dato l'impressione che la chiamata a GetInputReportViaInterruptTransfer () all'interno di RequestToGetInputReport () avrebbe atteso (e forse lo fa?) Ma non sembra la chiamata a RequestToGetInputReport () stesso sta aspettando perché mi sembra di rientrare nel ciclo while quasi immediatamente?

Qualcuno può chiarire il comportamento che sto vedendo?

Risposte:


131

Evitare async void. Chiedi ai tuoi metodi di restituire Taskinvece di void. Allora puoi awaitloro.

Come questo:

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

1
Molto bello grazie. Stavo graffiare la mia testa su un problema simile e la differenza era di cambiare voida Taskproprio come avevi detto.
Jeremy,

8
È una cosa minore, ma per seguire la convenzione entrambi i metodi dovrebbero avere Async aggiunto ai loro nomi, ad esempio RequestToGetInputReportAsync ()
tymtam

6
e se il chiamante è la funzione principale?
simbion

14
@symbiont: Quindi utilizzareGetAwaiter().GetResult()
Stephen Cleary il

4
@AhmedSalah The Taskrappresenta l'esecuzione del metodo, pertanto i returnvalori vengono inseriti Task.Resulte le eccezioni vengono impostate Task.Exception. Con void, il compilatore non ha nessun posto in cui inserire eccezioni, quindi vengono semplicemente rilanciate su un thread del pool di thread.
Stephen Cleary,

229

La cosa più importante da sapere asynced awaitè che await non attende il completamento della chiamata associata. Ciò che awaitfa è restituire immediatamente e in modo sincrono il risultato dell'operazione se l'operazione è già stata completata o, in caso contrario, pianificare una continuazione per eseguire il resto del asyncmetodo e quindi restituire il controllo al chiamante. Al termine dell'operazione asincrona, verrà eseguito il completamento pianificato.

La risposta alla domanda specifica nel titolo della domanda è bloccare il asyncvalore restituito di un metodo (che dovrebbe essere di tipo Tasko Task<T>) chiamando un Waitmetodo appropriato :

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 questo frammento di codice, CallGetFooAsyncAndWaitOnResultè un wrapper sincrono attorno al metodo asincrono GetFooAsync. Tuttavia, questo modello deve essere evitato per la maggior parte poiché bloccherà un intero thread di pool di thread per la durata dell'operazione asincrona. Questo è un uso inefficiente dei vari meccanismi asincroni esposti dalle API che fanno grandi sforzi per fornirli.

La risposta in "wait" non attende il completamento della chiamata ha diverse, più dettagliate, spiegazioni di queste parole chiave.

Nel frattempo, la guida di @Stephen Cleary sulle prese async void. Altre belle spiegazioni del perché possono essere trovate su http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ e https://jaylee.org/archive/ 2012/07/08 / do diesis-asincrone-tips-and-tricks-parte-2-async-void.html


18
Trovo utile pensare (e parlare) awaitcome "attesa asincrona" - cioè, blocca il metodo (se necessario) ma non il thread . Quindi ha senso parlare di RequestToSendOutputReport"aspettare" RequestToGetInputReportanche se non è un'attesa bloccante .
Stephen Cleary,

@Richard Cook - grazie mille per la spiegazione aggiuntiva!
bmt22033

10
Questa dovrebbe essere la risposta accettata, dal momento che risponde più chiaramente alla domanda reale (cioè come bloccare thread-saggio su un metodo asincrono).
CSV

la soluzione migliore è attendere asincrono fino a quando l'attività completata è var result = Task.Run (async () => {return waitit yourMethod ();}). Risultato;
Ram chittala,

70

La soluzione migliore per attendere AsynMethod fino al completamento dell'attività è

var result = Task.Run(async() => await yourAsyncMethod()).Result;

15
O questo per il tuo "vuoto" asincrono: Task.Run (async () => {await yourAsyncMethod ();}). Wait ();
Jiří Herník,

1
Qual è il vantaggio di questo rispetto a yourAsyncMethod (). Risultato?
Justin J Stark,

1
Il semplice accesso alla proprietà .Result in realtà non attende il completamento dell'esecuzione dell'attività. In effetti, credo che generi un'eccezione se viene chiamato prima che un'attività venga completata. Penso che il vantaggio di racchiuderlo in una chiamata Task.Run () sia che, come menziona Richard Cook di seguito, "wait" non attende effettivamente il completamento di un'attività, ma l'utilizzo di una chiamata .Wait () blocca l'intero pool di thread . Ciò consente di eseguire (in modo sincrono) un metodo asincrono su un thread separato. Leggermente confuso, ma eccolo qui.
Lucas Leblanc,

bello lanciare il risultato lì, proprio quello di cui avevo bisogno
Gerry

Promemoria rapido ECMA7 come assync () o in attesa non funzionerà in ambiente pre-ECMA7.
Mbotet,

0

Ecco una soluzione alternativa usando una bandiera:

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

-1

basta mettere Wait () per attendere fino al completamento dell'attività

GetInputReportViaInterruptTransfer().Wait();


Questo blocca il thread corrente. Quindi questa è di solito una brutta cosa da fare.
Pure.Krome

-5

Il frammento seguente mostra un modo per assicurarsi che il metodo atteso venga completato prima di tornare al chiamante. TUTTAVIA, non direi che è una buona pratica. Modifica la mia risposta con spiegazioni se la pensi diversamente.

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")

Downvoter, ti interessa spiegare, come ho chiesto nella risposta? Perché questo funziona bene per quanto ne so ...
Jerther

3
Il problema è che l'unico modo in cui puoi fare await+ Console.WriteLineè che diventa a Task, che abbandona il controllo tra i due. quindi la tua "soluzione" alla fine produrrà a Task<T>, che non risolve il problema. Fare una Task.Waitvolontà interromperà effettivamente l'elaborazione (con possibilità di deadlock ecc.). In altre parole, in awaitrealtà non aspetta, combina semplicemente due porzioni eseguibili in modo asincrono in un singolo Task(che qualcuno può guardare o aspettare)
Ruben Bartelink,

-5

In realtà l'ho trovato più utile per le funzioni che restituiscono IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.