2013-03-01 14 views
77

Sto scrivendo un'applicazione WinForms che trasferisce i dati su un dispositivo di classe HID USB. La mia applicazione utilizza l'eccellente libreria HID generica v6.0 che può essere trovata here. In poche parole, quando ho bisogno di scrivere i dati al dispositivo, questo è il codice che viene chiamato:Come attendere il completamento del metodo async?

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 cade fuori dal giro, mentre, ho bisogno di leggere alcuni dati dal dispositivo. Tuttavia, il dispositivo non è in grado di rispondere subito, quindi devo aspettare che questa chiamata torni prima di continuare. Nella sua forma attuale, RequestToGetInputReport() viene dichiarata 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() si presenta così:

internal async Task<int> GetInputReportViaInterruptTransfer() 

Purtroppo, io non sono molto familiare con il funzionamento delle nuove tecnologie async/await in .NET 4.5. Ho letto un po 'prima la parola chiave attendi e questo mi ha dato l'impressione che la chiamata a GetInputReportViaInterruptTransfer() all'interno di RequestToGetInputReport() attenda (e forse lo fa?) Ma non sembra la chiamata a RequestToGetInputReport() lo stesso è in attesa perché mi sembra di rientrare nel ciclo while quasi immediatamente?

Qualcuno può chiarire il comportamento che sto vedendo?

risposta

88

Evitare async void. I tuoi metodi restituiscono Task anziché void. Quindi puoi await loro.

Ti piace questa:

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

Grazie, Stephen. – user685869

+0

Molto carino, grazie. Mi stavo grattando la testa su un problema simile e la differenza era di cambiare 'void' in' Task' proprio come avevi detto. – Jeremy

+5

È una cosa secondaria, ma per seguire la convenzione entrambi i metodi dovrebbero avere Async aggiunto al loro nome, ad es. RequestToGetInputReportAsync() – mayu

128

La cosa più importante da sapere su async e await è che awaitnon attesa per la chiamata associata per il completamento. Che cosa fa await deve restituire il risultato dell'operazione immediatamente e in modo sincrono se l'operazione è già completata oppure, in caso contrario, pianificare una continuazione per eseguire il resto del metodo async e quindi restituire il controllo al chiamante . Al termine dell'operazione asincrona, verrà eseguito il completamento pianificato.

La risposta alla domanda specifica nel titolo del vostro domanda è per bloccare il valore di ritorno di un metodo di async (che dovrebbe essere di tipo Task o Task<T>) chiamando un apposito Wait metodo:

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 codice snippet, CallGetFooAsyncAndWaitOnResult è un sincrono wrapper attorno al metodo asincrono GetFooAsync. Tuttavia, questo schema deve essere evitato per la maggior parte in quanto bloccherà un intero thread del thread 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 a "await" doesn't wait for the completion of call ha diversi, più dettagliata, le spiegazioni di queste parole chiave.

Nel frattempo, la guida di @Stephen Cleary su async void detiene.Altre belle spiegazioni sul perché possono essere trovate a http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ e http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx.

+14

Trovo utile pensare (e parlare) di "attendere" come "attesa asincrona" - cioè, blocca il * metodo * (se necessario) ma non il * thread *. Quindi ha senso parlare di "RequestToSendOutputReport'" in attesa di "' RequestToGetInputReport' anche se non è un * blocco * wait. –

+0

@Richard Cook - grazie mille per la spiegazione aggiuntiva! – user685869

+5

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

-6

Il seguente frammento mostra un modo per garantire il completamento del metodo atteso prima di tornare al chiamante. TUTTAVIA, non direi che è una buona pratica. Per favore 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.") 
+0

Downvoters, cura di spiegare, come ho chiesto nella risposta? Perché questo funziona bene per quanto ne so ... – Jerther

+2

Il problema è che l'unico modo in cui puoi fare il comando 'await' + + 'Console.WriteLine' sta diventando un' Task', che dà il controllo tra i due . così la tua 'soluzione' alla fine darà un 'Compito ', che non risolve il problema. L'esecuzione di [Task.Atten'] (http://stackoverflow.com/questions/32075084/manage-update-in-application-exit/32075919#) interromperà l'elaborazione (con possibilità di deadlock, ecc.). In altre parole, 'await' in realtà non aspetta, unisce semplicemente due porzioni in modo asincrono eseguibili in un unico' Task' (che qualcuno possa guardare o aspettare) –

18

migliore soluzione per aspettare AsynMethod fino a completa il compito è

var result = Task.Run(async() => { return await yourAsyncMethod(); }).Result; 
+1

O questo per il vostro asincrono "vuoto": \t Task.Run (async() => {attende il tuoAsyncMethod();}). Wait(); –

Problemi correlati