Использование ConfigureAwait во вложенных асинхронных методах

#c# #async-await #blazor

#c# #асинхронное ожидание #blazor

Вопрос:

Я разрабатываю серверное веб-приложение Blazor, и я разрываюсь при использовании .ConfigureAwait(false) . Я знаю, что мне не следует использовать его в коде компонента, но я не уверен насчет вложенного кода. Например: я вызываю асинхронный метод для обновления данных на странице при нажатии кнопки. Сам этот метод также вызывает метод async для выполнения запроса к API. Вопрос в том, следует ли вызывать этот вложенный метод (запрос API) с ConfigureAwait(false) помощью или нет?

Редактировать: добавление кода для более наглядной демонстрации:

Этот метод вызывается с кнопки OnClick

 protected async Task GridRefresh()
{
    Models = null;
    _errorMessage = null;
    try
    {
        Models = await _dataService.GetDataAsync(Active);
    }
    catch (Exception ex)
    {
        _errorMessage = ex.Message.Split("Message:").Last();
    }
}
  

Этот метод находится в DataService классе, который я добавляю в свой компонент с помощью внедрения зависимостей

 public async Task<IEnumerable<ItemViewModel>> GetDataAsync(int id)
{
    var container = await ApiWrapper.getByIDAsync(id);
    if(container == null)
        throw new Exception($"Container with code {id} not found!");

    return container.Items;
}
  

Вопрос: должен ли я использовать ConfigureAwait GetDataAsync метод in?

 var container = await ApiWrapper.getByIDAsync(id).ConfigureAwait(false);
  

Комментарии:

1. Это зависит от того, что вы делаете после await . Можете ли вы показать пример своего кода?

2. Что вы имеете в виду nested ? Руководство предназначено не для компонентов , а для библиотек или «другого» кода в целом, чтобы позволить коду верхнего уровня решать, возвращаться ли к исходному коду синхронизации, например, к потоку пользовательского интерфейса. Библиотеки должны использовать ConfigureAwait(false) . Это касается вашего собственного кода, отличного от пользовательского интерфейса. Код пользовательского интерфейса не должен , потому что для обновления пользовательского интерфейса ему необходимо вернуться к исходному контексту

3. Пожалуйста, покажите свой код……

4. @TheodorZoulias Я добавил код для редактирования

Ответ №1:

Вы должны сделать то же самое, что и в настольных приложениях.

  • Чтобы обновить пользовательский интерфейс, вы должны быть в его контексте синхронизации, поэтому вам не следует использовать ConfigureAwait(false) при обновлении пользовательского интерфейса.
  • В коде, отличном от пользовательского интерфейса, нет необходимости возвращаться к контексту пользовательского интерфейса, поэтому вам, вероятно, следует использовать ConfigureAwait(false) и позволить коду верхнего уровня решить, нужно ли ему возвращаться к контексту пользовательского интерфейса. Таким образом, вы избегаете возможных взаимоблокировок, если поток / контекст пользовательского интерфейса занят.
  • В библиотечном коде вы должны использовать ConfigureAwait(false) , чтобы избежать принудительного переключения контекста и, возможно, взаимоблокировки

В небольших чистых компонентах (таких компонентах, которые вы должны создавать в любом случае) не должно быть такой большой проблемы, если вы опустите ConfigureAwait(false) .

В библиотечном коде пропуск ConfigureAwait(false) может легко привести к взаимоблокировкам, особенно во время отладки. На самом деле, мне интересно, вызваны ли тупики, с которыми сталкивается какой-либо популярный локальный пакет хранения, некоторыми пропущенными ConfigureAwait вызовами

Ответ №2:

Давайте рассмотрим GetDataAsync метод:

 public async Task<IEnumerable<ItemViewModel>> GetDataAsync(int id)
{
    var container = await ApiWrapper.getByIDAsync(id);
    if(container == null)
        throw new Exception($"Container with code {id} not found!");

    return container.Items;
}
  

После ожидания getByIDAsync будут выполнены две строки кода:

 if(container == null)
    throw new Exception($"Container with code {id} not found!");

return container.Items;
  

В настоящее время эти две строки будут выполняться в контексте синхронизации Блейзора. Требуется ли это? Это зависит от кода, выполняемого в методе доступа к Items свойству container объекта. Есть ли какой-либо код, который взаимодействует с каким-либо компонентом пользовательского интерфейса? Если да, или если вы не уверены, то вам не следует добавлять ConfigureAwait(false) ожидание getByIDAsync . Если нет (что наиболее вероятно), то использование ConfigureAwait(false) не будет иметь негативных последствий. Положительные аспекты добавления ConfigureAwait(false) — это то, что позволит вызывающей стороне блокировать асинхронный код без взаимоблокировки вашего приложения:

 Models = _dataService.GetDataAsync(Active).Resu< // Bad practice
  

Добавление ConfigureAwait(false) также сделает ваш код немного более эффективным. И под небольшим я подразумеваю практически незначительный. Так что основывайте свое решение не на этом, а на том, что делает код, и на том, как он предназначен для использования.

Ответ №3:

Те же правила применяются к любой структуре пользовательского интерфейса.

ConfigureAwait(false) , обеспечивает очень небольшую эффективность. Однако, как правило, вы бы не включали его в код пользовательского интерфейса верхнего уровня.

Чтобы ответить на вопрос напрямую:

Этот метод находится в классе DataService, который я добавляю в свой компонент с помощью внедрения зависимостей. должен ли я использовать ConfigureAwait в методе GetDataAsync?

В этом сценарии все в порядке.

Долгая история

Контекст синхронизации

Blazor использует контекст синхронизации (SynchronizationContext) для принудительного выполнения одного логического потока выполнения. Методы жизненного цикла компонента и любые обратные вызовы событий, вызываемые Blazor, выполняются в контексте синхронизации.

Контекст синхронизации сервера Blazor пытается эмулировать однопоточную среду, чтобы она точно соответствовала модели WebAssembly в браузере, которая является однопоточной. В любой данный момент времени работа выполняется ровно в одном потоке, создавая впечатление единого логического потока. Никакие две операции не выполняются одновременно.

Короче говоря, он выполняется в одном потоке пользовательского интерфейса и со своим собственным контекстом синхронизации, поэтому не вызывайте ConfigureAwait(false) код верхнего уровня, просто используйте ту же логику, что и в любой другой среде пользовательского интерфейса, которая имеет контекст синхронизации для перекачки сообщений или диспетчера.

Который, по-видимому, подкреплен SteveSandersonMS в этом вопросе github Blazor Вопрос: ConfigureAwait(false) в Blazor на стороне сервера?

Да, Blazor Server — это платформа пользовательского интерфейса, которая требует, чтобы ваш код выполнялся в контексте синхронизации. Поэтому не используйте ConfigureAwait(false) в коде вашего компонента Blazor.

а также из mkArtakMSFT в этом вопросе github Руководство по использованию ConfigureAwait(…) в асинхронных методах жизненного цикла в компоненте Razor

Наше руководство состоит в том, чтобы просто не использовать ConfigureAwait в Blazor.

Только для сценариев, связанных с пользовательским интерфейсом. Полезно для сценариев, не влияющих на пользовательский интерфейс (запрос хранилища данных или службы) ConfigureAwait(false) .

Комментарии:

1. Вопрос касается другого кода — кода, который не обновляет пользовательский интерфейс напрямую

2. @TheGeneral Я добавил код для редактирования, чтобы более точно понять, о чем я спрашиваю