#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. обычно лучше всего обобщить содержание статьи, которая фактически решила вашу проблему / ответила на ваш вопрос, а не просто ссылаться на нее. В противном случае, если эта ссылка исчезнет в будущем, и какой-нибудь бедный разработчик с тем же вопросом, что и у вас, столкнется с этим, они будут действительно опустошены, увидев, что ответ был потерян.