Skuteczne mieszanie metod synchronizacji i asynchronizacji w ramach jednej metody?

11

Ok, to brzmi dziwnie, ale kod jest bardzo prosty i dobrze wyjaśnia sytuację.

public virtual async Task RemoveFromRoleAsync(AzureTableUser user, string role)
{
    AssertNotDisposed();
    var roles = await GetRolesForUser(user);
    roles.Roles = RemoveRoles(roles.Roles, role);
    await Run(TableOperation.Replace(roles));
}

(Wiem, że mówię trochę w streszczeniu poniżej, ale powyższe jest faktyczną metodą z tego, co będzie rzeczywistym kodem produkcyjnym, który faktycznie robi to, o co tutaj pytam, i jestem naprawdę zainteresowany twoją recenzją dla poprawności względem wzorca asynchronicznego / oczekiwania).

Coraz częściej spotykam się z tym wzorcem, kiedy używam async/ awaitmore. Wzór składa się z następującego łańcucha zdarzeń:

  1. Poczekaj na pierwsze połączenie, które dostarczy mi informacji, nad którymi muszę pracować
  2. Pracuj nad tymi informacjami synchronicznie
  3. Poczekaj na ostatnie połączenie, które zapisuje zaktualizowaną pracę

Powyższy blok kodu to zazwyczaj sposób, w jaki zajmuję się obsługą tych metod. I awaitpierwsze połączenie, które muszę, ponieważ jest asynchroniczne. Następnie wykonuję pracę, którą muszę wykonać, która nie jest związana z operacjami wejścia / wyjścia ani zasobami, a więc nie jest asynchroniczna. Na koniec oszczędzam swoją pracę, która jest również asyncwezwaniem, i poza kultem ładunkowym awaitto ja .

Ale czy jest to najbardziej wydajny / prawidłowy sposób obsługi tego wzoru? Wydaje mi się, że mogę pominąć awaitostatnie połączenie, ale co jeśli się nie powiedzie? I czy powinienem zastosować Taskmetodę taką jak połączenie ContinueWithmojej pracy synchronicznej z pierwotnym wywołaniem? Jestem teraz w punkcie, w którym nie jestem pewien, czy radzę sobie z tym poprawnie.

Biorąc pod uwagę kod z przykładu , czy istnieje lepszy sposób na obsługę tego łańcucha wywołań metody async / sync / async?

Oszukany
źródło
Kod wydaje mi się tak krótki i zrozumiały, jak to możliwe. Wszystko, co mogę wymyślić, wprowadza niepotrzebną złożoność.
Euforyczny
@Euphoric: Czy powinienem zawracać sobie głowę oczekiwaniem na mój ostatni telefon? Co by się stało, gdybym tego nie zrobił i rzucił? Czy byłoby inaczej, jak jest obecnie?
Zgrane

Odpowiedzi:

3

Tak, myślę, że to właściwy sposób, aby to zrobić.

Nie możesz pominąć drugiego await. Jeśli tak, metoda wydaje się kończyć zbyt wcześnie (przed faktycznym usunięciem) i nigdy nie dowiesz się, czy usunięcie się nie powiodło.

Nie rozumiem, jak by ContinueWith()to pomogło. Możesz go użyć, aby tego uniknąć await, ale spowodowałoby to, że Twój kod byłby bardziej skomplikowany i mniej czytelny. I o to właśnie chodzi await: uprościć pisanie kodu asynchronicznego w porównaniu z kontynuacją.

svick
źródło
0

Sposób obsługi tego wzorca polega na zapewnieniu, że wszystkie operacje we / wy są asynchroniczne. Synchroniczne metody we / wy powodują blokowanie bieżącego wątku podczas oczekiwania na odpowiedź z miejsca docelowego we / wy (sieć, system plików itp.).

Inną rzeczą do rozważenia jest to, że awaitpowinno się jej używać, gdy potrzebujesz wartości zwracanej lub gdy potrzebujesz kodu, aby zakończyć działanie przed zrobieniem czegoś innego. Jeśli nie potrzebujesz żadnej z tych rzeczy, możesz „odpalić i zapomnieć” swoją metodę asynchroniczną Task.Run.

Tak więc, w celu jak najbardziej efektywnego wykorzystania zasobów obliczeniowych, jeśli RemoveRolesjakieś I / O, powinno się stać, await RemoveRolesAsynca wywoływane metody I / O RemoveRolesAsyncrównież powinny być asynchroniczne (i być może oczekiwane).

Jeśli wydajność nie jest najważniejsza, możesz wykonać synchroniczne operacje we / wy w wątku asynchronicznym. Jest to jednak dług techniczny. (W tym przypadku, to może chcesz się połączyć pierwszą metodę transmisji asynchronicznej z ConfigureAwait, w zależności od tego, gdzie jest uruchomiony kod).

Oto bardziej dogłębne spojrzenie na najlepsze praktyki - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Oto kilka uwag na temat zachowania ConfigureAwait w różnych środowiskach, takich jak ASP.NET, WebAPI itp. - /programming/13489065/best-practice-to-call-configureawait-for-all-server-side -kod

Wayne Bloss
źródło
2
Nigdy nie powinieneś odpalać i zapomnieć kodu z Task.Run. Wszystkie zadania powinny być oczekiwane. Jeśli nie czekasz na zadanie, a zadanie jest odśmiecane w stanie, w którym wyjątek jest „nieobserwowany”, spowoduje to wygenerowanie wyjątku UnobservedTaskException w czasie wykonywania i może spowodować awarię aplikacji w zależności od wersji frameworka.
Triynko