2013-03-01 26 views
77

Piszę aplikację WinForms, która przesyła dane do urządzenia klasy USB HID. Moja aplikacja używa doskonałej biblioteki Generic HID w wersji 6.0, którą można znaleźć pod adresem here. W skrócie, gdy trzeba zapisać dane do urządzenia, jest to kod, który pobiera nazywane:Jak czekać na zakończenie metody asynchronicznej?

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

Kiedy mój kod spada z pętli while, muszę przeczytać kilka danych z urządzenia. Jednak urządzenie nie jest w stanie odpowiedzieć od razu, więc muszę czekać na to wezwanie, aby powrócić, zanim przejdę dalej. Jako że obecnie istnieje, RequestToGetInputReport() jest zadeklarowana następująco:

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

Na co warto, zgłoszenie o GetInputReportViaInterruptTransfer() wygląda następująco:

internal async Task<int> GetInputReportViaInterruptTransfer() 

Niestety, nie jestem bardzo obeznany z funkcjonowaniem nowych technologii asynchronicznych/oczekujących w .NET 4.5. Czytałem wcześniej trochę o oczekiwanym słowie kluczowym i to dało mi wrażenie, że wywołanie GetInputReportViaInterruptTransfer() wewnątrz RequestToGetInputReport() czekałoby (i może to robi?), Ale to nie wydaje się być wezwaniem do RequestToGetInputReport() sam czeka, ponieważ wydaje mi się, że niemal natychmiast powracam do pętli while?

Czy ktoś może wyjaśnić zachowanie, które widzę?

Odpowiedz

88

Należy unikać async void. Czy twoje metody zwracają Task zamiast void. Wtedy możesz je await.

Jak to:

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

Dziękuję, Stephen. – user685869

+0

Bardzo ładne, dziękuję. Podrapałam się w podobny problem, a różnica polegała na tym, że zmieniłaś 'void' na' Task', tak jak powiedziałeś. – Jeremy

+5

To drobnostka, ale zgodnie z konwencją obie metody powinny mieć asynchroniczne nazwy, np. RequestToGetInputReportAsync() – mayu

128

Najważniejszą rzeczą jest wiedzieć o async i await że awaitnie czekać na wezwanie do uzupełnienia związane. To, co robi await, to natychmiastowe i synchroniczne zwrócenie wyniku operacji, jeśli operacja została już zakończona: lub, jeśli tak się nie stało, zaplanowanie kontynuacji wykonywania pozostałej części metody async, a następnie zwrócenie kontroli do osoby dzwoniącej. . Po zakończeniu operacji asynchronicznej wykonanie zaplanowane zostanie wykonane.

Odpowiedź na konkretne pytanie w tytule Twojego pytanie jest do blokowania o wartości zwracają async metody badaniem (które powinny być typu Task lub Task<T>) poprzez wywołanie odpowiedniego Wait metody:

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

W tym kodzie snippet, CallGetFooAsyncAndWaitOnResult jest opakowaniem synchronicznym wokół metody asynchronicznej GetFooAsync. Jednak ten wzorzec należy unikać w większości przypadków, ponieważ blokuje on wątek całej nici na czas trwania operacji asynchronicznej. Jest to nieefektywne wykorzystanie różnych mechanizmów asynchronicznych udostępnianych przez interfejsy API, które dokładają wszelkich starań, aby je zapewnić.

Odpowiedź na "await" doesn't wait for the completion of call zawiera kilka bardziej szczegółowych wyjaśnień tych słów kluczowych.

Tymczasem instrukcje @Stephen Cleary dotyczące async void posiadają.Inne ładne wyjaśnienia, dlaczego można znaleźć na http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ i http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx.

+14

Uważam, że warto pomyśleć (i mówić) o 'czekaj' jako" asynchroniczne oczekiwanie "- to znaczy blokuje * metodę * (jeśli to konieczne) ale nie * wątek *. Więc warto mówić o 'RequestToSendOutputReport'" czekając na "' RequestToGetInputReport', nawet jeśli nie jest to * blokowanie * oczekiwania. –

+0

@Richard Cook - bardzo dziękuję za dodatkowe wyjaśnienie! – user685869

+5

Powinna to być zaakceptowana odpowiedź, ponieważ lepiej odpowiada na faktyczne pytanie (np. Jak nić-blokować metodę asynchroniczną). – csvan

-6

Poniższy fragment przedstawia sposób zagwarantowania, że ​​oczekiwana metoda zakończy się przed powrotem do osoby dzwoniącej. JEDNAK, nie powiedziałbym, że to dobra praktyka. Proszę edytować moją odpowiedź z objaśnieniami, jeśli myślisz inaczej.

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

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

Downwotrzy, czy trzeba wyjaśniać, tak jak pytałem w odpowiedzi? Ponieważ działa to dobrze, o ile wiem ... – Jerther

+2

Problem polega na tym, że jedynym sposobem na zrobienie 'await' +' Console.WriteLine' staje się 'Task', który rezygnuje z kontroli pomiędzy dwoma . więc twoje "rozwiązanie" ostatecznie da "zadanie ", które nie rozwiązuje problemu. Wykonanie zadania ['Task.Wait'] (http://stackoverflow.com/questions/32075084/manage-update-in-application-exit/32075919#) faktycznie przerwie przetwarzanie (z możliwością zakleszczenia itd.). Innymi słowy, 'await' w rzeczywistości nie czeka, po prostu łączy dwie asynchronicznie wykonywalne części w jedno' Zadanie' (które ktoś może oglądać lub czekać) –

18

najlepsze rozwiązanie czekać AsynMethod do wykonania zadania jest

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

Albo to dla twojego asynchronicznego "void": \t Task.Run (async() => {czekaj na yourAsyncMethod();}). Czekaj(); –

Powiązane problemy