Найкраща практика для виклику ConfigureAwait для всього коду на стороні сервера


561

Якщо у вас є код на стороні сервера (тобто деякий ApiController), і ваші функції асинхронні - тому вони повертаються Task<SomeObject>- чи вважається найкращою практикою будь-який час, коли ви очікуєте функцій, які ви викликаєте ConfigureAwait(false)?

Я читав, що він є більш ефективним, оскільки йому не потрібно перемикати контексти потоків назад до початкового контексту потоку. Однак із ASP.NET Web Api, якщо ваш запит надходить на один потік, і ви очікуєте деякої функції та дзвінка, ConfigureAwait(false)який потенційно може перевести вас на інший потік, коли ви повертаєте кінцевий результат своєї ApiControllerфункції.

Я набрав приклад того, про що я говорю нижче:

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;
    }
}

Відповіді:


628

Оновлення: ASP.NET Core не маєSynchronizationContext . Якщо ви перебуваєте на ASP.NET Core, не має значення, використовуєте ви його ConfigureAwait(false)чи ні.

Для ASP.NET "Повний" або "Класичний" або що завгодно, решта цієї відповіді все ще стосується.

Оригінальна публікація (для непрофільних ASP.NET):

Це відео команди ASP.NET має найкращу інформацію про використання asyncна ASP.NET.

Я читав, що він є більш ефективним, оскільки йому не потрібно перемикати контексти потоків назад до початкового контексту потоку.

Це вірно з програмами інтерфейсу, де є лише одна нитка інтерфейсу, до якої ви повинні "синхронізувати" назад.

В ASP.NET ситуація дещо складніша. Коли asyncметод відновить виконання, він захоплює нитку з пулу потоків ASP.NET. Якщо ви вимкнете захоплення контексту за допомогою ConfigureAwait(false), то потік просто продовжує виконувати метод безпосередньо. Якщо ви не відключите захоплення контексту, то потік знову введе контекст запиту, а потім продовжить виконання методу.

Тому ConfigureAwait(false)не врятує вас стрибок потоку в ASP.NET; це врятує вас від повторного введення в контекст запиту, але це, як правило, дуже швидко. ConfigureAwait(false) може бути корисним, якщо ви намагаєтеся виконати невелику кількість паралельної обробки запиту, але насправді TPL найкраще підходить для більшості таких сценаріїв.

Однак із ASP.NET Web Api, якщо ваш запит надходить на один потік, і ви очікуєте певної функції і зателефонуєте ConfigureAwait (false), який потенційно може перевести вас на інший потік, коли ви повертаєте кінцевий результат функції ApiController .

Власне, просто робити це awaitможе зробити це. Як тільки ваш asyncметод потрапляє на await, метод блокується, але потік повертається до пулу потоків. Коли метод готовий до продовження, будь-яка нитка відривається з пулу потоків і використовується для відновлення методу.

Єдина відмінність ConfigureAwaitу ASP.NET полягає в тому, чи входить цей потік у контекст запиту під час відновлення методу.

Я маю більше довідкової інформації у своїй статті MSDNSynchronizationContext та в своєму asyncвступному дописі в блозі .


23
Локальне сховище потоків не передається жодним контекстом. HttpContext.Currentпередається через ASP.NET SynchronizationContext, який за замовчуванням передається, коли ви await, але це не передається ContinueWith. OTOH, контекст виконання (включаючи обмеження безпеки) - це контекст, згаданий у CLR через C #, і він передається обома ContinueWithта await(навіть якщо ви використовуєте ConfigureAwait(false)).
Стівен Клірі

65
Не було б чудово, якби у C # була підтримка рідної мови для ConfigureAwait (false)? Щось на кшталт 'awaitnc' (очікувати без контексту). Введення окремого виклику методу скрізь досить дратує. :)
NathanAldenSr

19
@NathanAldenSr: Про це йшлося досить багато. Проблема з новим ключовим словом полягає в тому, що ConfigureAwaitнасправді має сенс лише тоді, коли ви очікуєте завдань , тоді як awaitдіє на будь-які "очікувані". Інші параметри, що розглядалися, були такі: чи слід відмовлятися від контексту поведінки за умовчанням, якщо він знаходиться в бібліотеці? Або є налаштування компілятора для поведінки контексту за замовчуванням? І те й інше було відхилено, оскільки важче просто прочитати код і розповісти, що він робить.
Стівен Клірі

10
@AnshulNigam: Ось чому дії контролера потребують свого контексту. Але більшість методів, які викликають дії, не мають.
Стівен Клірі

14
@JonathanRoeder: Взагалі кажучи, вам не потрібно ConfigureAwait(false)уникати тупикового блоку Result/ на Waitоснові, оскільки в ASP.NET ви не повинні використовувати Result/ Waitв першу чергу.
Стівен Клірі

131

Коротка відповідь на ваше запитання: Ні. Вам не слід дзвонити ConfigureAwait(false)на рівні заявки таким чином.

TL; DR версія тривалої відповіді: Якщо ви пишете бібліотеку, де ви не знаєте свого споживача і не потребуєте контексту синхронізації (якого ви не повинні в бібліотеці, я вважаю), ви завжди повинні використовувати ConfigureAwait(false). В іншому випадку споживачі вашої бібліотеки можуть зіткнутися з тупиками, використовуючи ваші асинхронні методи блокуючим способом. Це залежить від ситуації.

Ось трохи більш детальне пояснення важливості ConfigureAwaitметоду (цитата з мого повідомлення в блозі):

Коли ви чекаєте на метод з ключовим словом await, компілятор генерує купу коду від імені вас. Однією з цілей цієї дії є обробка синхронізації з потоком UI (або основним). Ключовим компонентом цієї функції є те, SynchronizationContext.Currentщо отримує контекст синхронізації для поточного потоку. SynchronizationContext.Currentзаселяється залежно від середовища, в якому ви перебуваєте. GetAwaiterМетод Завдання шукає SynchronizationContext.Current. Якщо поточний контекст синхронізації не є нульовим, продовження, яке передається цьому офіціанту, буде опубліковано до цього контексту синхронізації.

Під час використання методу, який використовує нові функції асинхронної мови, блокуючи, ви закінчитеся тупиком, якщо у вас є доступний SynchronizationContext. Коли ви використовуєте такі методи блокуючим способом (очікуючи методу "Завдання з очікуванням" або отримуючи результат безпосередньо із властивості "Результат" Завдання), ви одночасно заблокуєте основну нитку. Коли врешті-решт Задача завершиться всередині цього методу в нитковій пулі, вона посилається на продовження для публікації назад до основної нитки, оскільки SynchronizationContext.Currentвона доступна та захоплена. Але тут є проблема: потік користувальницького інтерфейсу заблокований, і у вас тупик!

Крім того, ось дві чудові статті для вас, які саме стосуються вашого питання:

Нарешті, є чудове коротке відео від Лучана Вищика саме на цю тему: Методи бібліотеки Async повинні розглянути можливість використання Task.ConfigureAwait (false) .

Сподіваюсь, це допомагає.


2
"Метод GetAwaiter Завдання шукає SynchronizationContext.Current. Якщо поточний контекст синхронізації не є нульовим, продовження, яке передається цьому офіціанту, буде опубліковано до цього контексту синхронізації." - У мене складається враження, що ви намагаєтесь сказати, що Taskходить стеком, щоб отримати SynchronizationContext, що неправильно. SynchronizationContextХапають до виклику до Taskа потім решти коду триває на , SynchronizationContextякщо SynchronizationContext.Currentне дорівнює нулю.
casperОдин

1
@casperOne Я мав намір сказати те саме.
тугберк

8
Чи не повинно бути відповідальність за абонента, щоб переконатися, що SynchronizationContext.Currentце чітко / або що бібліотека викликається в межах Task.Run()замість того, щоб писати .ConfigureAwait(false)по всій бібліотеці класів?
binki

1
@binki - з іншого боку: (1) імовірно, що бібліотека використовується у багатьох програмах, тому одноразові зусилля в бібліотеці для полегшення роботи програм є економічно вигідними; (2) імовірно, автор бібліотеки знає, що він написав код, який не має підстав вимагати продовження в оригінальному контексті, який він виражає цими .ConfigureAwait(false)s. Можливо, авторам бібліотеки було б простіше, якби це було поведінкою за замовчуванням, але я б припустив, що зробити трохи складніше правильно написати бібліотеку - це краще, ніж трохи складніше правильно написати додаток.
ToolmakerSteve

4
Чому автор бібліотеки повинен утискувати споживача? Якщо споживач хоче зайти в глухий кут, чому я повинен їх запобігати?
Quarkly

25

Найбільший недолік, який я виявив за допомогою ConfigureAwait (false), полягає в тому, що культура потоків повертається до системного замовчування. Якщо ви налаштували культуру, наприклад ...

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

і ви хостите на сервері, для культури якого встановлено en-US, ви знайдете перед тим, як ConfigureAwait (false) називається CultureInfo.CurrentCulture поверне en-AU і після того, як ви отримаєте en-US. тобто

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

Якщо ваша програма робить все, що вимагає форматування даних, що стосуються конкретної культури, вам потрібно пам’ятати про це під час використання ConfigureAwait (false).


27
Сучасні версії .NET (я думаю, що з 4.6?) Поширюватимуть культуру в потоках, навіть якщо ConfigureAwait(false)вони використовуються.
Стівен Клірі

1
Дякую за інформацію. Ми дійсно використовуємо .net 4.5.2
Мік

11

У мене є загальні думки щодо впровадження Task:

  1. Завдання одноразове, але ми не повинні використовувати using.
  2. ConfigureAwaitбуло введено в 4,5. Taskбуло введено в 4.0.
  3. .NET Threads завжди використовувались для потоку контексту (див. C # через CLR-книгу), але в реалізації за замовчуванням Task.ContinueWithвони не b / c це було зрозуміло, що контекстний перемикач дорогий і він вимкнено за замовчуванням.
  4. Проблема полягає в тому, що розробник бібліотеки не повинен дбати, чи потребує її клієнтів контекстний потік чи ні, отже, він не повинен вирішувати, передати контекст чи ні.
  5. [Додано пізніше] Те, що немає авторитетної відповіді та належної довідки, і ми продовжуємо боротися з цим, означає, що хтось не виконав свою роботу правильно.

У мене є кілька дописів на цю тему, але я вважаю, що, крім приємної відповіді Тугберка, - це те, що ви повинні перетворити всі API на асинхронний і в ідеалі перетікати контекст. Оскільки ви робите асинхронізацію, ви можете просто використовувати продовження замість того, щоб чекати, тому жодна тупикова ситуація не стане причиною, оскільки в бібліотеці не чекають, і ви зберігаєте потоковий, щоб контекст зберігався (наприклад, HttpContext).

Проблема полягає в тому, коли бібліотека відкриває синхронний API, але використовує інший асинхронний API - отже, вам потрібно використовувати Wait()/ Resultу своєму коді.


6
1) Ви можете зателефонувати, Task.Disposeякщо хочете; вам просто не потрібно для переважної більшості часу. 2) Taskбув представлений у .NET 4.0 у складі TPL, який не потребував ConfigureAwait; коли asyncбуло додано, вони повторно використовували існуючий Taskтип замість того, щоб вигадувати новий Future.
Стівен Клірі

6
3) Ви плутаєте два різних типи "контексту". "Контекст", згаданий у C # через CLR, завжди протікає навіть у Tasks; "контекст", яким керує, ContinueWithє SynchronizationContextабо TaskScheduler. Ці різні контексти детально пояснюються у блозі Стівена Туба .
Стівен Клірі

21
4) Автору бібліотеки не потрібно дбати про те, чи потребує її абонентів контекстний потік, оскільки кожен асинхронний метод поновлюється незалежно. Отже, якщо абонентам потрібен контекстний потік, вони можуть передавати його, незалежно від того, передав йому автор бібліотеки чи ні.
Стівен Клірі

1
Спочатку ти, здається, скаржишся замість того, щоб відповісти на запитання. І тоді ви говорите про "контекст", за винятком того, що в .Net є кілька видів контексту, і насправді незрозуміло, про який (або з них?) Ви говорите. І навіть якщо ви не плутаєте себе (але я думаю, що ви є, я вважаю, що немає контексту, який раніше використовувався з Threads, але вже не з цим ContinueWith()), це робить вашу відповідь незрозумілою для читання.
svick

1
@StephenCleary так, Lib Dev не повинен знати, це залежить від клієнта. Я думав, що це я зрозумів, але моє фразування було не ясно.
Аліостад
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.