Najlepsza praktyka wywoływania ConfigureAwait dla całego kodu po stronie serwera

Jeśli masz kod po stronie serwera (tj. niektóre ApiController) i twoje funkcje są asynchroniczne-więc zwracają Task<SomeObject> - czy uważa się za najlepszą praktykę, że za każdym razem czekasz na funkcje, które wywołujesz ConfigureAwait(false)?

Czytałem, że jest bardziej wydajny, ponieważ nie musi zmieniać kontekstu wątku z powrotem na oryginalny kontekst wątku. Jednak z ASP.NET Web Api, jeśli twoja prośba pojawia się w jednym wątku i czekasz na jakąś funkcję i wywołanie ConfigureAwait(false), które potencjalnie może umieścić cię na inny wątek, gdy zwracasz końcowy wynik swojej funkcji ApiController.

Wpisałem poniżej przykład tego, o czym mówię:

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
Author: abatishchev, 2012-11-21

4 answers

Aktualizacja: ASP.NET rdzeń nie posiada SynchronizationContext. Jeśli jesteś na ASP.NET Core, nie ma znaczenia czy używasz ConfigureAwait(false) czy nie.

Dla ASP.NET "pełny" lub "klasyczny" czy cokolwiek innego, reszta tej odpowiedzi nadal obowiązuje.

Original post (for non-Core ASP.NET):

This video by the ASP.NET zespół ma najlepsze informacje o używaniu async na ASP.NET.

Czytałem, że jest bardziej wydajny, ponieważ nie trzeba przełączyć konteksty wątku z powrotem na oryginalny kontekst wątku.

Dotyczy to aplikacji UI, gdzie istnieje tylko jeden wątek UI, do którego musisz" zsynchronizować".

W ASP.NET, sytuacja jest nieco bardziej złożona. Gdy metoda async wznawia wykonywanie, chwyta wątek z ASP.NET Pula wątków. Jeśli wyłączysz przechwytywanie kontekstu za pomocą ConfigureAwait(false), wtedy wątek po prostu kontynuuje wykonywanie metody bezpośrednio. Jeśli nie wyłączysz przechwytywania kontekstu, to wątek ponownie wprowadzi kontekst żądania, a następnie kontynuuje wykonywanie metody.

Więc ConfigureAwait(false) nie zapisuje Ci wątku ASP.NET; zapisuje to ponowne wprowadzenie kontekstu żądania, ale zwykle jest to bardzo szybkie. ConfigureAwait(false) może być użyteczne, jeśli próbujesz wykonać niewielką ilość równoległego przetwarzania żądania, ale tak naprawdę TPL lepiej pasuje do większości tych scenariuszy.

Jednak z ASP.NET Web Api, jeśli twoje żądanie przychodzi na jeden thread, i czekasz na jakąś funkcję i wywołanie ConfigureAwait (false), które potencjalnie mogą umieścić cię w innym wątku, gdy zwracasz końcowy wynik funkcji ApiController.

Właściwie, samo zrobienie await może to zrobić. Gdy twoja metoda async trafi w await, metoda zostanie zablokowana, ale wątek powróci do puli wątków. Gdy metoda jest gotowa do kontynuowania, każdy wątek jest wyrwany z puli wątków i używany do wznowienia metoda.

Jedyna różnica ConfigureAwait sprawia, że w ASP.NET czy wątek wchodzi w kontekst żądania podczas wznawiania metody.

Mam więcej informacji w moim MSDN artykuł na SynchronizationContext i mój async intro blog post .

 457
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-23 23:23:58

Krótka odpowiedź na twoje pytanie: nie. Nie powinieneś tak wywoływać ConfigureAwait(false) na poziomie aplikacji.

TL; DR wersja długiej odpowiedzi: jeśli piszesz bibliotekę, w której nie znasz swojego konsumenta i nie potrzebujesz kontekstu synchronizacji (którego nie powinieneś w bibliotece, jak sądzę), powinieneś zawsze używać ConfigureAwait(false). W przeciwnym razie użytkownicy biblioteki mogą napotkać impasy, zużywając metody asynchroniczne w sposób blokujący. To zależy od sytuacji.

Tutaj jest nieco bardziej szczegółowe wyjaśnienie znaczenia metody ConfigureAwait (cytat z mojego posta na blogu):

Gdy oczekujesz na metodę ze słowem kluczowym wait, kompilator generuje kod w Twoim imieniu. Jednym z celów tego akcja polega na obsłudze synchronizacji z wątkiem UI (lub głównym). Klucz składową tej funkcji jest SynchronizationContext.Current, która pobiera kontekst synchronizacji dla bieżącego wątku. SynchronizationContext.Current jest wypełniana w zależności od środowisko, w którym się znajdujesz. GetAwaiter metoda zadania wyszukuje dla SynchronizationContext.Current. Jeśli aktualny kontekst synchronizacji jest nie null, kontynuacja, która zostanie przekazana do tego wysłane z powrotem do kontekstu synchronizacji.

Podczas używania metody, która używa nowego języka asynchronicznego funkcje, w sposób blokujący, skończysz z impasem, jeśli masz dostępną Synchronizacjękontekst. Podczas spożywania takie metody w sposób blokujący (czekanie na zadanie z Wait metoda lub pobierając wynik bezpośrednio z właściwości Result Zadanie), zablokujesz główny wątek w tym samym czasie. Kiedy ostatecznie zadanie kończy się wewnątrz tej metody w threadpool, to będzie wywoływać kontynuację, aby dodać powrót do głównego wątku ponieważ SynchronizationContext.Current jest dostępna i przechwycona. Ale jest tu problem: wątek UI jest zablokowany i masz impas!

Również, oto dwa świetne Artykuły dla Ciebie, które są dokładnie dla Twojego pytania:

Wreszcie, jest świetny Krótki film z Lucian Wischik dokładnie na ten temat: metody biblioteki asynchronicznej powinny rozważyć użycie zadania.ConfigureAwait (false) .

Hope this pomaga.

 113
Author: tugberk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-13 09:12:37

Największą wadą, jaką znalazłem przy użyciu ConfigureAwait (false), jest to, że kultura wątków jest przywracana do domyślnej wartości systemowej. Jeśli skonfigurowałeś kulturę np...

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

I jesteś hosting na serwerze, którego kultura jest ustawiona na en-US, to znajdziesz przed ConfigureAwait (false) nazywa się CultureInfo.CurrentCulture zwróci en-AU, a następnie otrzymasz en-US. tj.

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

Jeśli Twoja aplikacja robi coś, co wymaga specyficznego formatowania danych, wtedy musisz o tym pamiętać podczas używania ConfigureAwait (false).

 8
Author: Mick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-22 03:12:32

Mam kilka ogólnych przemyśleń na temat implementacji Task:

  1. zadanie jest jednorazowe, a my nie powinniśmy używać using.
  2. ConfigureAwait został wprowadzony w 4.5. Task został wprowadzony w 4.0.
  3. . Net wątki zawsze używane do przepływu kontekstu (patrz C# poprzez książkę CLR), ale w domyślnej implementacji Task.ContinueWith nie b / c okazało się, że przełącznik kontekstowy jest drogi i jest domyślnie wyłączony.
  4. problemem jest biblioteka deweloper nie powinien dbać o to, czy jego klienci potrzebują przepływu kontekstu, czy nie, dlatego nie powinien decydować, czy przepływ kontekstu, czy nie.
  5. [Dodano później] fakt, że nie ma autorytatywnej odpowiedzi i właściwego odniesienia, a my walczymy o to oznacza, że ktoś nie wykonał swojej pracy dobrze.

Mam kilka postów na ten temat, ale moim zdaniem - oprócz ładnej odpowiedzi Tugberka - jest to, że powinieneś włączyć wszystkie API asynchroniczne i najlepiej przepłynąć kontekst . ponieważ wykonujesz asynchronizację, możesz po prostu użyć kontynuacji zamiast czekać, aby nie spowodować impasu, ponieważ nie ma czekania w bibliotece i zachowujesz przepływ, aby kontekst był zachowany (np. HttpContext).

Problem polega na tym, że biblioteka wystawia synchroniczne API, ale używa innego asynchronicznego API - dlatego musisz użyć Wait()/Result w Twoim kodzie.

 7
Author: Aliostad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-07 16:25:01