Рекомендуется вызывать ConfigureAwait для всего кода на стороне сервера

342

Когда у вас есть код на стороне сервера (т.е. некоторая 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;
    }
}
Теги:
async-await
asp.net-web-api
task-parallel-library

4 ответа

359
Лучший ответ

Обновление: У ASP.NET Core нет SynchronizationContext. Если вы используете ASP.NET Core, неважно, используете ли вы ConfigureAwait(false) или нет.

Для ASP.NET "Полный" или "Классик" или что-то еще, остальная часть этого ответа по-прежнему применяется.

Исходный пост (для неосновного ASP.NET):

Это видео команды ASP.NET имеет лучшую информацию об использовании async на ASP.NET.

Я читал, что он более эффективен, поскольку ему не нужно переключать контексты потоков обратно в исходный контекст потока.

Это верно в приложениях UI, где есть только один поток пользовательского интерфейса, на который вы должны "синхронизировать" обратно.

В 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 заключается в том, входит ли этот поток в контекст запроса при возобновлении метода.

У меня есть дополнительная информация в моей статье MSDN на SynchronizationContext и async вступление в блог.

  • 0
    Мой ответ был удален, поэтому я не могу ответить вам там. Но я не путаю контексты здесь, я не знаю о вас. Под контекстом понимаются данные области хранения потоков. Контекст не передается в ContinueWith по умолчанию - точка. Данные TSA не копируются - пожалуйста, докажите, что я не прав, если вы думаете иначе. Вы можете проверить это, посмотрев на HttpContext.Current . Вот почему мы проходим через обручи и обручи, чтобы течь это.
  • 22
    Локальное хранилище потока не передается никаким контекстом. HttpContext.Current через ASP.NET SynchronizationContext , который HttpContext.Current по умолчанию, когда вы await , но не передается через ContinueWith . Ото, контекст исполнения ( в том числе ограничений безопасности) контекст упоминается в CLR через C #, и она потекла оба ContinueWith и await (даже если вы используете ConfigureAwait(false) ).
Показать ещё 33 комментария
98

Краткий ответ на ваш вопрос: Нет. Вы не должны называть ConfigureAwait(false) на уровне приложения таким образом.

TL; DR-версия длинного ответа: если вы пишете библиотеку, где вы не знаете своего потребителя, и вам не нужен контекст синхронизации (который вы не должны в библиотеке, я считаю), вы всегда должны используйте ConfigureAwait(false). В противном случае потребители вашей библиотеки могут столкнуться с взаимоблокировками, блокируя ваши асинхронные методы. Это зависит от ситуации.

Ниже приведено более подробное объяснение важности метода ConfigureAwait (цитата из моего сообщения в блоге):

Когда вы ждёте метод с ключевым словом await, компилятор генерирует кучу кода для вас. Одна из целей этого действие - обрабатывать синхронизацию с потоком пользовательского интерфейса (или основного). Ключ компонентом этой функции является SynchronizationContext.Current, которая получает контекст синхронизации для текущего потока. SynchronizationContext.Current заполняется в зависимости от в которой вы находитесь. Метод GetAwaiter Task ищет SynchronizationContext.Current. Если текущий контекст синхронизации не null, продолжение, которое передается этому awaiter, получит отправлен обратно в этот контекст синхронизации.

При использовании метода, который использует новый асинхронный язык функции, в блокирующем режиме, вы окажетесь в тупике, если у вас есть доступный SynchronizationContext. Когда вы потребляете такие методы в блокирующем режиме (ожидание задачи с ожиданием метода или получения результата непосредственно из свойства Result Задача), вы будете блокировать основной поток одновременно. когда в конечном итоге задача завершается внутри этого метода в threadpool, это собирается вызвать продолжение, чтобы отправить обратно в основной поток потому что SynchronizationContext.Current доступен и захвачен. Но здесь есть проблема: поток пользовательского интерфейса заблокирован, и у вас есть тупиковый!

Кроме того, вот две большие статьи для вас, которые именно для вашего вопроса:

Наконец, есть отличное короткое видео из Lucian Wischik именно по этой теме: Методам библиотеки Async следует рассмотреть возможность использования Task.ConfigureAwait(false).

Надеюсь, что это поможет.

  • 2
    «Метод GetAwaiter Task выполняет поиск SynchronizationContext.Current. Если текущий контекст синхронизации не равен нулю, продолжение, переданное этому ожидающему, будет отправлено обратно в этот контекст синхронизации». - У меня создается впечатление, что вы пытаетесь сказать, что Task обходит стек, чтобы получить SynchronizationContext , что неправильно. SynchronizationContext захватывается перед вызовом Task а затем остальная часть кода продолжается в SynchronizationContext если SynchronizationContext.Current не равен NULL.
  • 1
    @casperOne Я собирался сказать то же самое.
Показать ещё 7 комментариев
1

Самый большой откат, который я нашел с помощью 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).

  • 11
    Современные версии .NET (я думаю, начиная с 4.6?) Будут распространять культуру между потоками, даже если используется ConfigureAwait(false) .
  • 1
    Спасибо за информацию. Мы действительно используем .net 4.5.2
1

У меня есть общие мысли о реализации Task:

  • Задача одноразовая, но мы не должны использовать using.
  • ConfigureAwait был введен в 4.5. Task был введен в 4.0.
  • .NET Threads всегда используется для потока контекста (см. С# через CLR-книгу), но в реализации по умолчанию Task.ContinueWith они не выполняли b/c, а контекстный переключатель был дорогостоящим и по умолчанию отключен.
  • Проблема заключается в том, что разработчик библиотеки не заботится о том, нужен ли ее клиентам поток контекста или нет, поэтому он не должен решать, вытекает ли поток контекст или нет.
  • [Добавлено позже] Тот факт, что нет авторитетного ответа и правильной ссылки, и мы продолжаем бороться, это означает, что кто-то не выполнил свою работу правильно.

У меня есть несколько сообщений на эту тему, но мой прием - помимо хорошего ответа Tugberk - заключается в том, что вам следует превратите все API в асинхронный и идеальный поток контекста.. Поскольку вы выполняете async, вы можете просто использовать продолжения, а не ждать, так что никакой взаимоблокировки не будет, потому что в библиотеке нет ожидания, и вы держите поток таким образом, что контекст (например, HttpContext).

Проблема в том, что библиотека предоставляет синхронный API, но использует другой асинхронный API, поэтому вам нужно использовать Wait()/Result в вашем коде.

  • 6
    1) Вы можете вызвать Task.Dispose если хотите; Вам просто не нужно подавляющее большинство времени. 2) Task была введена в .NET 4.0 как часть TPL, которая не нуждалась в ConfigureAwait ; когда async была добавлена, они повторно использовали существующий тип Task вместо того, чтобы придумывать новое Future .
  • 6
    3) Вы путаете два разных типа «контекста». «Контекст», упомянутый в C # через CLR, всегда передается, даже в Task s; «контекст», управляемый ContinueWith представляет собой SynchronizationContext или TaskScheduler . Эти различные контексты подробно объясняются в блоге Стивена Туба .
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню