Нужно ли нам делать все методы асинхронными

#c# #async-await #task-parallel-library

#c# #async-await #задача-параллельная-библиотека

Вопрос:

Я реализовал API, в котором есть контроллер, у которого есть дерево вызовов, как показано ниже.

 public async Task<Collection<United.Service.Presentation.LoyaltyModel.Program>> GetRewardPrograms(string languageCode)
{
           
                return await _referenceDataDomain.GetRewardPrograms(languageCode).ConfigureAwait(false);
            
          
}
  
     public async Task<Collection<Program>> GetRewardPrograms(string languageCode)
        {
            return  await _referenceDataProvider.GetRewardPrograms(languageCode).ConfigureAwait(false);
        }
  
  public async Task<Collection<Program>> GetRewardPrograms(string languageCode)
        {
           
            if (string.IsNullOrEmpty(languageCode))
            {
                languageCode = _constants.LANGUAGE_CODE_EN_US;
            }
            var rewardProgramsSet = new Collection<Program>();
            var format = CacheKeysDictionary.CacheKeyFormat(CacheKeysDictionary.RewardPrograms);
            var cacheKey = string.Format(format, languageCode);

            var cacheValue = await _cacheUtility.GetCacheItemAsync<Collection<Program>>(cacheKey).ConfigureAwait(false);
            if (cacheValue != null amp;amp; cacheValue.Any())
                return cacheValue;

            if (_commonConfig.UseLoyaltyService())
            {
                try
                {
                    var cslHttpClient = _serviceProvider.GetRequiredService<ICSLServiceProxy>();
                    var queryStringParams = new NameValueCollection() { { "languageCode", languageCode } };

                    var result = await cslHttpClient.GetAsync<NameValueCollection, RewardProgramsReferenceData>(_commonConfig.LoyaltyServiceUrl(),
                                                "ReferenceDataRewardProgram/idType/a", queryStringParams, _commonConfig.TimeOutDefault());

                    if (result != null amp;amp; result.ResponseData != null amp;amp; result.ResponseData.referenceDataRewardProgramList.Count > 0)
                    {
                        foreach (var item in result.ResponseData.referenceDataRewardProgramList)
                        {
                            var program = new Program
                            {
                                ProgramID = item.ProgramID.ToInt32(),
                                Code = item.ProgramCode,
                                Description = item.Description,
                                Language = new Language { LanguageCode = languageCode }
                            };

                            rewardProgramsSet.Add(program);
                        }
                    }
                }
                catch { }
            }

            if (rewardProgramsSet != null amp;amp; rewardProgramsSet.Any())
            {
                await _cacheUtility.SetCacheItemAsync(cacheKey, rewardProgramsSet).ConfigureAwait(false);
            }

            return rewardProgramsSet;
        }
  

Я получил отзыв code review о том, что, поскольку я делаю все методы асинхронными, а второй блок кода не выполняет никаких асинхронных операций, я должен вернуть task вместо использования метода async, поскольку добавление async внутренне превратило бы мой метод в конечный автомат, и это создало бы некоторые проблемы с производительностью, рецензент направил мне эту статью
https://medium.com/@deep_blue_day/long-story-short-async-await-best-practices-in-net-1f39d7d84050 , пожалуйста, кто-нибудь может подсказать мне, есть ли недостаток в вышеупомянутом подходе.

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

1. @Dave спасибо за ваш ответ, обновленный второй блок, у него есть квалификатор async, в данном конкретном случае он ничего не делает, а просто возвращает результат, но в других случаях у нас реализована бизнес-логика, и все наши методы используют refdataprovider через объект домена, поэтому для согласованности мы выполняем этот вызов

2. Это называется пересылкой задачи , это экономит IAsyncStateMachine реализацию и дает вам небольшую эффективность, но необходимо позаботиться о том, чтобы метод не мог выдавать себя, иначе вам нужно было бы перехватить исключение и поместить его в задачу, чтобы придерживаться семантики шаблона асинхронности и ожидания . Я полагаю, что где-то есть блог Стивена Клири, который затрагивает эту тему. В обзоре просто говорилось, что вы можете безопасно удалить ключевые слова async и await из вашего второго примера, чтобы перенаправить задачу

3. Как сказал Майкл, удаление асинхронности в этих сценариях может иметь небольшое влияние на производительность (но оно небольшое, поэтому вам нужно часто вызывать его или в контексте, чувствительном к производительности, чтобы заметить) — Я как-то читал, что разворачивание на каждом уровне, подобном этому, может оказать некоторое влияние на обработку исключений и трассировку стека, но я не могу вспомнить, и это вполне может быть то же самое, что сказал Майкл.

4. Это очень хорошая статья для чтения: blog.stephencleary.com/2016/12/eliding-async-await.html

5. @saurabhvats Ссылка на статью на самом деле не является ответом. Не стесняйтесь добавлять свои собственные 🙂

Ответ №1:

Я получил свой ответ из этой статьи, опубликованной Стефаном https://blog.stephencleary.com/2016/12/eliding-async-await.html , Спасибо Джонатану за предоставление ссылки

следующий абзац из приведенной выше ссылки является ответом на мой вопрос:

Рекомендуемые рекомендации Я предлагаю следовать этим рекомендациям:

Не исключайте по умолчанию. Используйте async и ожидайте естественного, легко читаемого кода. Рассмотрите возможность исключения, когда метод является просто проходом или перегрузкой.

 // Simple passthrough to next layer: elide.
Task<string> PassthroughAsync(int x) => _service.PassthroughAsync(x);

// Simple overloads for a method: elide.
async Task<string> OverloadsAsync(CancellationToken cancellationToken)
{
    ... // Core implementation, using await.
}
Task<string> OverloadsAsync() => OverloadsAsync(CancellationToken.None);

// Non-trivial passthrough: use keywords.
async Task<string> PassthroughAsync(int x)
{
    // Reasoning: GetFirstArgument can throw.
    //  Even if it doesn't throw today, some yahoo can change it tomorrow, and it's not possible for them to know to change *this* method, too.
    return await _service.PassthroughAsync(GetFirstArgument(), x);
}

// Non-trivial overloads for a method: use keywords.
async Task<string> OverloadsAsync()
{
    // Same reasoning as above; GetDefaultCancellationTokenForThisScope can throw.
    return await OverloadsAsync(GetDefaultCancellationTokenForThisScope());
}
  

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

1. обычно лучше всего обобщить содержание статьи, которая фактически решила вашу проблему / ответила на ваш вопрос, а не просто ссылаться на нее. В противном случае, если эта ссылка исчезнет в будущем, и какой-нибудь бедный разработчик с тем же вопросом, что и у вас, столкнется с этим, они будут действительно опустошены, увидев, что ответ был потерян.