Jak czekać na zakończenie metody asynchronicznej?

138

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ę?

bmt22033
źródło

Odpowiedzi:

131

Unikaj async void. Niech twoje metody zwracają Taskzamiast void. Wtedy możesz awaitje.

Lubię 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();
}
Stephen Cleary
źródło
1
Bardzo ładnie, dziękuje. Drapałem się po głowie w podobnym temacie i różnica polegała na tym, że zmieniłem voidsię Tasktak, jak powiedziałeś.
Jeremy,
8
To drobiazg, ale aby zachować konwencję, obie metody powinny mieć Async dodane do ich nazw, np. RequestToGetInputReportAsync ()
tymtam
6
a co, jeśli dzwoniący jest główną funkcją?
symbiont
14
@symbiont: Następnie użyjGetAwaiter().GetResult()
Stephen Cleary
4
@AhmedSalah The Taskreprezentuje wykonanie metody - więc returnwartości są umieszczane na Task.Result, a wyjątki są umieszczane na Task.Exception. W voidprzypadku kompilator nie ma miejsca na umieszczanie wyjątków, więc są one ponownie zgłaszane w wątku puli wątków.
Stephen Cleary
229

Najważniejszą rzeczą, aby wiedzieć asynci awaitto, że await nie czekać na towarzyszącym wezwanie do wykonania. To, co awaitrobi, 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ęści asyncmetody, 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 asyncwartości zwracanej metody (która powinna być typu Tasklub Task<T>) poprzez wywołanie odpowiedniej Waitmetody:

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 fragmencie kodu CallGetFooAsyncAndWaitOnResultjest synchroniczne opakowanie wokół metody asynchronicznej GetFooAsync. 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 voidblokad. 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

Richard Cook
źródło
18
Uważam, że warto pomyśleć (i porozmawiać) o await„asynchronicznym oczekiwaniu” - to znaczy, że blokuje metodę (jeśli to konieczne), ale nie wątek . Dlatego warto mówić o RequestToSendOutputReport„czekaniu”, RequestToGetInputReportnawet jeśli nie jest to blokujące czekanie.
Stephen Cleary
@Richard Cook - bardzo dziękuję za dodatkowe wyjaśnienia!
bmt22033
10
Powinna to być akceptowana odpowiedź, ponieważ jaśniej odpowiada na rzeczywiste pytanie (tj. Jak wykonać blokowanie wątków w metodzie asynchronicznej).
csvan
najlepszym rozwiązaniem jest czekanie asynchroniczne na zakończenie zadania. var result = Task.Run (async () => {return await yourMethod ();}). Wynik;
Ram chittala
70

Najlepszym rozwiązaniem, aby poczekać AsynMethod do zakończenia zadania, jest

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram chittala
źródło
15
Albo to dla twojego async "void": Task.Run (async () => {await yourAsyncMethod ();}). Wait ();
Jiří Herník,
1
Jaka jest korzyść z tego w stosunku do yourAsyncMethod (). Result?
Justin J Stark,
1
Po prostu uzyskanie dostępu do właściwości .Result nie jest w rzeczywistości czekaniem do zakończenia wykonywania zadania. W rzeczywistości uważam, że zgłasza wyjątek, jeśli zostanie wywołany przed zakończeniem zadania. Myślę, że zaletą zawijania tego w wywołaniu Task.Run () jest to, że, jak wspomina poniżej Richard Cook, „await” nie czeka na zakończenie zadania, ale użycie wywołania .Wait () blokuje całą pulę wątków . Umożliwia to (synchroniczne) uruchamianie metody asynchronicznej w oddzielnym wątku. Nieco zagmatwane, ale tak jest.
Lucas Leblanc
niezłe wrzucenie wyniku, właśnie tego potrzebowałem
Gerry
Szybkie przypomnienie Funkcja ECMA7, taka jak assync () lub oczekiwanie, nie będzie działać w środowisku poprzedzającym ECMA7.
Mbotet
0

Oto obejście za pomocą flagi:

//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();
}
szokujący lemi
źródło
-1

wystarczy umieścić Wait (), aby poczekać, aż zadanie zostanie ukończone

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
źródło
To blokuje bieżący wątek. Więc zazwyczaj jest to zła rzecz.
Pure.Krome
-4

W rzeczywistości okazało się, że jest to bardziej przydatne w przypadku funkcji, które zwracają IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Barış Tanyeri
źródło
-5

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.

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
źródło
Downvoters, chcecie wyjaśnić, tak jak pytałem w odpowiedzi? Ponieważ to działa dobrze, o ile wiem ...
Jerther
3
Problem polega na tym, że jedyny sposób, w jaki możesz zrobić await+ Console.WriteLineto, to stać się a Task, co oznacza utratę kontroli między nimi. więc twoje „rozwiązanie” ostatecznie da wynik a Task<T>, co nie rozwiązuje problemu. Robi Task.Waitbędzie rzeczywiście zaprzestania przetwarzania (z możliwości zakleszczenia etc). Innymi słowy, awaittak naprawdę nie czeka, po prostu łączy dwie asynchronicznie wykonywalne części w jedną Task(którą ktoś może oglądać lub czekać)
Ruben Bartelink