it-swarm.com.ru

Лучшая практика для вызова ConfigureAwait для всего кода на стороне сервера

Если у вас есть код на стороне сервера (т.е. некоторые 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;
    }
}
472
Arash Emami

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

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

Оригинальный пост (для не Core 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 заключается в том, входит ли этот поток в контекст запроса при возобновлении метода.

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

527
Stephen Cleary

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

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

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

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

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

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

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

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

122
tugberk

Самый большой недостаток, который я обнаружил при использовании 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).

15
Mick

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

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

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

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

8
Aliostad