Piszę aplikację WinForms, która przesyła dane do urządzenia USB klasy HID. Moja aplikacja korzysta z doskonałej biblioteki Generic HID v6.0, którą można znaleźć tutaj . W skrócie, kiedy muszę zapisać dane do urządzenia, jest to kod, który jest wywoływany:
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 wypadnie z pętli while, muszę odczytać niektóre dane z urządzenia. Jednak urządzenie nie może od razu odpowiedzieć, więc muszę poczekać, aż to połączenie powróci, zanim będę kontynuować. W obecnej formie RequestToGetInputReport () jest zadeklarowana w następujący sposób:
private async void RequestToGetInputReport()
{
// lots of code prior to this
int bytesRead = await GetInputReportViaInterruptTransfer();
}
Na co warto zwrócić uwagę, deklaracja GetInputReportViaInterruptTransfer () wygląda następująco:
internal async Task<int> GetInputReportViaInterruptTransfer()
Niestety nie jestem zbyt zaznajomiony z działaniem nowych technologii async / await w .NET 4.5. Poczytałem trochę wcześniej o słowie kluczowym await i to dało mi wrażenie, że wywołanie GetInputReportViaInterruptTransfer () wewnątrz RequestToGetInputReport () będzie czekać (a może tak jest?), Ale nie wygląda na to, że wywołanie RequestToGetInputReport () samo czeka, ponieważ prawie natychmiast ponownie wchodzę w pętlę while?
Czy ktoś może wyjaśnić zachowanie, które widzę?
źródło
void
sięTask
tak, jak powiedziałeś.GetAwaiter().GetResult()
Task
reprezentuje wykonanie metody - więcreturn
wartości są umieszczane naTask.Result
, a wyjątki są umieszczane naTask.Exception
. Wvoid
przypadku kompilator nie ma miejsca na umieszczanie wyjątków, więc są one ponownie zgłaszane w wątku puli wątków.Najważniejszą rzeczą, aby wiedzieć
async
iawait
to, żeawait
nie czekać na towarzyszącym wezwanie do wykonania. To, coawait
robi, 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 wykonania pozostałej częściasync
metody, a następnie zwrócenie kontroli do obiektu wywołującego. Po zakończeniu operacji asynchronicznej zostanie wykonane zaplanowane zakończenie.Odpowiedzią na konkretne pytanie w tytule pytania jest zablokowanie
async
wartości zwracanej metody (która powinna być typuTask
lubTask<T>
) poprzez wywołanie odpowiedniejWait
metody:W tym fragmencie kodu
CallGetFooAsyncAndWaitOnResult
jest synchroniczne opakowanie wokół metody asynchronicznejGetFooAsync
. Jednak w większości przypadków należy unikać tego wzorca, ponieważ blokuje on cały wątek puli wątków na czas trwania operacji asynchronicznej. Jest to nieefektywne wykorzystanie różnych mechanizmów asynchronicznych udostępnianych przez interfejsy API, które wkładają wiele wysiłku w ich udostępnienie.Odpowiedź na "czekaj" nie czeka na zakończenie rozmowy ma kilka bardziej szczegółowych wyjaśnień tych słów kluczowych.
W międzyczasie wskazówki @Stephen Cleary dotyczące
async void
blokad. Inne miłe wyjaśnienia, dlaczego można znaleźć na http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ i https://jaylee.org/archive/ 2012/07/08 / c-sharp-async-tips-and-tricks-part-2-async-void.htmlźródło
await
„asynchronicznym oczekiwaniu” - to znaczy, że blokuje metodę (jeśli to konieczne), ale nie wątek . Dlatego warto mówić oRequestToSendOutputReport
„czekaniu”,RequestToGetInputReport
nawet jeśli nie jest to blokujące czekanie.Najlepszym rozwiązaniem, aby poczekać AsynMethod do zakończenia zadania, jest
źródło
Oto obejście za pomocą flagi:
źródło
wystarczy umieścić Wait (), aby poczekać, aż zadanie zostanie ukończone
GetInputReportViaInterruptTransfer().Wait();
źródło
W rzeczywistości okazało się, że jest to bardziej przydatne w przypadku funkcji, które zwracają IAsyncAction.
źródło
Poniższy fragment kodu pokazuje sposób, aby zapewnić zakończenie oczekiwanej metody przed powrotem do obiektu wywołującego. JEDNAK nie powiedziałbym, że to dobra praktyka. Zmień moją odpowiedź, podając wyjaśnienia, jeśli sądzisz inaczej.
źródło
await
+Console.WriteLine
to, to stać się aTask
, co oznacza utratę kontroli między nimi. więc twoje „rozwiązanie” ostatecznie da wynik aTask<T>
, co nie rozwiązuje problemu. RobiTask.Wait
będzie rzeczywiście zaprzestania przetwarzania (z możliwości zakleszczenia etc). Innymi słowy,await
tak naprawdę nie czeka, po prostu łączy dwie asynchronicznie wykonywalne części w jednąTask
(którą ktoś może oglądać lub czekać)