Dlaczego miałbyś kiedykolwiek „oczekiwać” na metodę, a następnie natychmiast sprawdzać jej wartość zwracaną?

24

W tym artykule MSDN znajduje się następujący przykładowy kod (lekko zredagowany dla zwięzłości):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

FindAsyncSposób pobiera Departmentprzedmiotu jego identyfikatora, i zwraca Task<Department>. Następnie dział jest natychmiast sprawdzany, aby sprawdzić, czy jest pusty. Jak rozumiem, zapytanie o wartość Zadania w ten sposób zablokuje wykonanie kodu, dopóki wartość z oczekiwanej metody nie zostanie zwrócona, co skutecznie spowoduje wywołanie synchroniczne.

Dlaczego miałbyś to robić? Czy nie byłoby łatwiej po prostu wywołać metodę synchroniczną Find(id), jeśli i tak zamierzasz natychmiast zablokować?

Robert Harvey
źródło
Może to być związane z wdrożeniem. ... else return null;Następnie musisz sprawdzić, czy metoda rzeczywiście znalazła żądany dział.
Jeremy Kato,
Nie widzę żadnego w asp.net, ale w aplikacji destop, robiąc to w ten sposób, nie zamrażasz interfejsu użytkownika
Rémi
Oto link wyjaśniający koncepcję oczekiwania od projektanta ... msdn.microsoft.com/en-us/magazine/hh456401.aspx
Jon Raynor
Oczekuje się, że warto pomyśleć o ASP.NET tylko wtedy, gdy przełączniki styków wątku spowalniają Cię lub użycie pamięci przez wiele stosów wątków jest dla Ciebie problemem.
Ian

Odpowiedzi:

24

Jak rozumiem, zapytanie o wartość Zadania w ten sposób zablokuje wykonanie kodu, dopóki wartość z oczekiwanej metody nie zostanie zwrócona, co skutecznie spowoduje wywołanie synchroniczne.

Nie do końca.

Po wywołaniu await db.Departments.FindAsync(id)zadanie jest wysyłane, a bieżący wątek jest zwracany do puli w celu użycia przez inne operacje. Przepływ wykonania jest zablokowany (tak jak by to było niezależnie od użycia departmentzaraz po, jeśli dobrze rozumiem), ale sam wątek może być używany przez inne rzeczy podczas oczekiwania na zakończenie operacji poza maszyną (i sygnalizowane przez port zdarzenia lub zakończenia).

Jeśli zadzwoniłeś, d.Departments.Find(id)wątek tam siedzi i czeka na odpowiedź, nawet jeśli większość przetwarzania jest wykonywana na DB.

Podczas wiązania dysku skutecznie zwalniasz zasoby procesora.

Telastyn
źródło
2
Ale myślałem, że wszystko, co awaitzrobiłem, to znak na pozostałej części metody jako kontynuacja tego samego wątku (są wyjątki; niektóre metody asynchroniczne rozwijają swój własny wątek) lub znak na asyncmetodzie jako kontynuacja tego samego wątku i pozwolenie na pozostały kod do wykonania (jak widać, nie jestem krystalicznie jasny, jak asyncdziała). To, co opisujesz, brzmi bardziej jak wyrafinowana formaThread.Sleep(untilReturnValueAvailable)
Robert Harvey,
1
@RobertHarvey - przypisuje go jako kontynuację, ale po wysłaniu zadania (i kontynuacji) do przetworzenia nie ma już nic do uruchomienia. Nie ma gwarancji, że będzie w tym samym wątku, chyba że określisz to (przez ConfigureAwaitiirc).
Telastyn
1
Zobacz moją odpowiedź ... kontynuacja jest domyślnie przeniesiona do oryginalnego wątku.
Michael Brown,
2
Myślę, że widzę tutaj to, czego mi brakuje. Aby to przyniosło jakiekolwiek korzyści, środowisko ASP.NET MVC musi awaitdo niego zadzwonić public async Task<ActionResult> Details(int? id). W przeciwnym razie oryginalne połączenie zostanie po prostu zablokowane, czekając na department == nullrozwiązanie.
Robert Harvey,
2
@RobertHarvey Do czasu await ...„powrotu” FindAsyncpołączenie zostało zakończone. To właśnie czeka. Nazywa się to czekaniem, ponieważ powoduje, że kod czeka na różne rzeczy. (Zauważ jednak, że to nie to samo, co sprawienie, że bieżący wątek zaczeka na rzeczy)
user253751,
17

Naprawdę nienawidzę tego, że żaden z przykładów nie pokazuje, jak można poczekać kilka linijek przed oczekiwaniem na zadanie. Rozważ to.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

Ten rodzaj kodu zachęca przykłady i masz rację. To nie ma sensu. Uwalnia główny wątek do robienia innych rzeczy, takich jak reagowanie na dane wejściowe w interfejsie użytkownika, ale prawdziwą mocą asynchronizacji / oczekiwania jest to, że mogę łatwo robić inne rzeczy, czekając na zakończenie potencjalnie długiego zadania. Powyższy kod „zablokuje się” i zaczeka na wykonanie wiersza wydruku, aż otrzymamy Foo & Bar. Nie trzeba jednak czekać. Możemy to przetworzyć, czekając.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

Teraz, dzięki przepisanemu kodowi, nie zatrzymujemy się i nie czekamy na nasze wartości, dopóki nie będziemy musieli. Zawsze szukam tego rodzaju możliwości. Sprytne określenie, kiedy czekamy, może doprowadzić do znacznej poprawy wydajności. W dzisiejszych czasach mamy wiele rdzeni, równie dobrze moglibyśmy z nich korzystać.

Gumowa kaczuszka
źródło
1
Hm, to mniej na temat rdzeni / wątków, a więcej na temat właściwego używania wywołań asynchronicznych.
Deduplicator,
Mylisz się @Deduplicator. Dlatego w odpowiedzi zacytowałem „blok”. Trudno jest rozmawiać na ten temat bez wspominania o wątkach, ale wciąż zdając sobie sprawę, że może to dotyczyć wielu wątków lub nie.
RubberDuck,
Sugeruję, abyś był ostrożny w oczekiwaniu na wywołanie innej metody. Czasami kompilator źle rozumie to oczekiwanie i dostajesz naprawdę dziwny wyjątek. Po tym, jak wszystkie async / await to tylko cukier składniowy, możesz sprawdzić w sharplab.io, jak wygląda wygenerowany kod. Zauważyłem to kilkakrotnie i teraz czekam tylko na jedną linię nad rozmową, gdzie potrzebny jest wynik ... nie potrzebuję tych bólów głowy.
eksmisja
„To nie ma sensu”. - Ma w tym wiele sensu. Przypadki, w których „robienie innych rzeczy” ma zastosowanie w tej samej metodzie, nie są tak powszechne; jest o wiele bardziej powszechne, że po prostu chcesz awaiti pozwalasz wątkowi robić zupełnie inne rzeczy.
Sebastian Redl,
Kiedy powiedziałem „nie ma w tym sensu” @SebastianRedl, miałem na myśli przypadek, w którym można oczywiście uruchomić oba zadania równolegle, zamiast uruchamiać, czekać, biegać, czekać. Jest to o wiele bardziej powszechne, niż się wydaje. Założę się, że jeśli rozejrzysz się po swojej bazie kodu, znajdziesz możliwości.
RubberDuck
6

Więc dzieje się więcej za kulisami. Async / Await to cukier składniowy. Najpierw spójrz na podpis funkcji FindAsync. Zwraca zadanie. Już widzisz magię słowa kluczowego, to rozpakowuje to Zadanie w Departamencie.

Funkcja wywołania nie blokuje się. To, co się dzieje, polega na tym, że przypisanie do działu i wszystko, co następuje po słowach kluczowych oczekujących, jest zamykane i dla wszystkich zamiarów i celów przekazywane do metody Task.ContinueWith (funkcja FindAsync jest automatycznie wykonywana w innym wątku).

Oczywiście dzieje się więcej za kulisami, ponieważ operacja jest przenoszona z powrotem do oryginalnego wątku (więc nie musisz się już martwić synchronizacją z interfejsem użytkownika podczas wykonywania operacji w tle) oraz w przypadku funkcji wywołującej Async ( i nazywany asynchronicznie) to samo dzieje się na stosie.

Tak więc dzieje się tak, gdy dostajesz magię operacji asynchronicznych bez pułapek.

Michael Brown
źródło
1

Nie, nie wraca natychmiast. Oczekiwanie powoduje, że wywołanie metody jest asynchroniczne. Po wywołaniu FindAsync metoda Details zwraca zadanie, które nie zostało ukończone. Po zakończeniu FindAsync zwróci swój wynik do zmiennej departamentu i wznowi resztę metody Details.

Steve
źródło
Nie, w ogóle nie ma blokowania. Nigdy nie przejdzie do departamentu == null, dopóki metoda asynchroniczna nie będzie już zakończona.
Steve,
1
async awaitgeneralnie nie tworzy nowych wątków, a nawet jeśli tak, nadal musisz poczekać, aż wartość tego działu ustali, czy jest ona pusta.
Robert Harvey,
2
Więc w zasadzie to, co mówisz, jest to, że aby to mieć dowolną rzecz w ogóle, wywołanie public async Task<ActionResult> należy również awaited.
Robert Harvey,
2
@RobertHarvey Tak, masz pomysł. Async / Await jest zasadniczo wirusowy. Jeśli „czekasz” na jedną funkcję, powinieneś również poczekać na funkcje, które ją wywołują. awaitnie należy go mieszać z .Wait()lub .Result, ponieważ może to powodować zakleszczenia. Łańcuch asynchroniczny / oczekujący zwykle kończy się funkcją z async voidsygnaturą, która jest najczęściej używana w procedurach obsługi zdarzeń lub w funkcjach wywoływanych bezpośrednio przez elementy interfejsu użytkownika.
KChaloux,
1
@ Steve - „ ... to byłoby na swoim własnym wątku. ” Nie, czytaj Zadania nadal nie są wątkami i asynchronizacja nie jest równoległa . Obejmuje to rzeczywiste wyniki pokazujące, że nowy wątek nie jest tworzony.
ToolmakerSteve
1

Lubię myśleć o „asynchronizacji” jak o umowie, która mówi: „mogę wykonać to asynchronicznie, jeśli zajdzie taka potrzeba, ale możesz do mnie zadzwonić jak każdą inną funkcję synchroniczną”.

Oznacza to, że jeden programista tworzył funkcje, a niektóre decyzje projektowe skłoniły ich do utworzenia / oznaczenia szeregu funkcji jako „asynchronicznych”. Osoba wywołująca / konsument funkcji może z nich korzystać zgodnie z decyzją. Jak mówisz, możesz albo zadzwonić, czekając tuż przed wywołaniem funkcji i poczekać na to, w ten sposób potraktowałeś to jak funkcję synchroniczną, ale jeśli chcesz, możesz zadzwonić bez oczekiwania jako

Task<Department> deptTask = db.Departments.FindAsync(id);

i po, powiedzmy, 10 liniach w dół funkcji, którą wywołujesz

Department d = await deptTask;

stąd traktując to jako funkcję asynchroniczną.

To zależy od Ciebie.

użytkownik734028
źródło
1
To bardziej jak „Zastrzegam sobie prawo do asynchronicznego obsługiwania wiadomości, użyj tego do uzyskania wyniku”.
Deduplicator,
-1

„jeśli i tak chcesz natychmiast zablokować”, odpowiedź brzmi „Tak”. Tylko wtedy, gdy potrzebujesz szybkiej odpowiedzi, czekaj / asynchronizacja ma sens. Na przykład wątek interfejsu użytkownika przechodzi do metody naprawdę asynchronicznej, wątek interfejsu użytkownika powraca i kontynuuje nasłuchiwanie kliknięcia przycisku, podczas gdy kod poniżej „Oczekiwanie” zostanie wycięty przez inny wątek i ostatecznie uzyska wynik.

użytkownik10200952
źródło
1
Co ta odpowiedź zapewnia, że ​​inne odpowiedzi nie?