Async-wait - Разве я это делаю?

Недавно у меня возникли сомнения в том, как я реализую шаблон async-wait в проектах веб-API. Я читал, что асинхронный ожидание должно быть "полностью" и тем, что я сделал. Но все это начинает казаться излишним, и я не уверен, что делаю это правильно. У меня есть контроллер, который вызывает репозиторий, и он вызывает класс доступа к данным (сущность framework 6) - "async all the way". Я прочитал много противоречивых материалов по этому вопросу и хотел бы, чтобы оно было выяснено.

EDIT: ссылка на возможный дубликат - хорошая публикация, но недостаточно для моих нужд. Я включил код, чтобы проиллюстрировать проблему. Кажется, действительно трудно получить решающий ответ на это. Было бы неплохо, если бы мы могли поставить async-wait в одном месте и позволить .net обрабатывать все остальное, но мы не можем. Итак, я над этим делаю или это не так просто.

Вот что у меня есть:

Контроллер:

public async Task<IHttpActionResult> GetMessages()
{
    var result = await _messageRepository.GetMessagesAsync().ConfigureAwait(false);
    return Ok(result);
}

Repository:

public async Task<List<string>> GetMessagesAsync()    
{
    return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}

Данные:

public async Task<List<string>> GetMessagesAsync()
{           
    return await _context.Messages.Select(i => i.Message).ToListAsync().ConfigureAwait(false);
}

Ответ 1

public async Task<List<string>> GetMessagesAsync()    
{
    return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}

public async Task<List<string>> GetMessagesAsync()
{           
    return await _context.Messages.Select(i => i.Message).ToListAsync().ConfigureAwait(false);
}

Если только вызовы, которые вы выполняете для асинхронных методов, являются обратными вызовами, вам действительно не нужно await:

public Task<List<string>> GetMessagesAsync()    
{
    return _referralMessageData.GetMessagesAsync();
}

public Task<List<string>> GetMessagesAsync()
{           
    return _context.Messages.Select(i => i.Message).ToListAsync();
}

О единственном, что вы теряете, это некоторая информация о трассировке стека, но это редко все, что полезно. Удалите await, а затем создайте машину состояния, которая обрабатывает ожидание, когда вы просто передадите задачу, вызванную вызываемым методом, до вызывающего метода, а метод вызова может await на этом.

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

Я даже зашел так далеко, чтобы превратить пути, не связанные с задачами, в задачи, если это было относительно просто:

public async Task<List<string>> GetMeesagesAsync()
{
   if(messageCache != null)
     return messageCache;
   return await _referralMessageData.GetMessagesAsync().ConfigureAwait(false);
}

становится:

public Task<List<string>> GetMeesagesAsync()
{
   if(messageCache != null)
     return Task.FromResult(messageCache);
   return _referralMessageData.GetMessagesAsync();
}

Однако, если в какой-то момент вам понадобятся результаты задачи для дальнейшей работы, тогда await ing - путь.

Ответ 2

Было бы неплохо, если бы мы могли поставить async-wait в одном месте и позволить .net обрабатывать остальные, но мы не можем. Итак, я над этим делаю или это не так просто.

Было бы неплохо, если бы это было проще.

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

На стороне примечания, репозиторий образца страдает от общей проблемы с репозиторией: ничего не делать. Если остальная часть вашего репозитория реального мира схожа, у вас может быть один уровень абстракции слишком много в вашей системе. Обратите внимание, что Entity Framework уже является общим хранилищем работоспособности.

Но в отношении async и await в общем случае код часто должен работать после await:

public async Task<IHttpActionResult> GetMessages()
{
  var result = await _messageRepository.GetMessagesAsync();
  return Ok(result);
}

Помните, что async и await - просто причудливый синтаксис для подключения обратных вызовов. Существует не простой способ выразить эту логику метода асинхронно. Были некоторые эксперименты вокруг, например, вывод await, но все они были отброшены на этом этапе (у меня есть сообщение в блоге, описывающее почему async/await ключевые слова имеют все "крутые", которые они выполняют).

И этот рывок необходим для каждого метода. Каждый метод с использованием async/await устанавливает свой собственный обратный вызов. Если обратный вызов не нужен, то метод может просто вернуть задачу напрямую, избегая async/await. Другие асинхронные системы (например, promises в JavaScript) имеют одно и то же ограничение: они должны быть асинхронными полностью.

Возможно - концептуально - определить систему, в которой любая операция блокировки автоматически выдаст поток. Мой главный аргумент против такой системы заключается в том, что она будет иметь неявное повторное включение. В частности, при рассмотрении изменений в библиотеке сторонних разработчиков автоматическая система уступки была бы незаменимой ИМО. Гораздо лучше иметь асинхронность явного API в своей сигнатуре (т.е., Если она возвращает Task, то она асинхронна).

Теперь @usr делает хороший вывод, что, возможно, вам вообще не нужна асинхронность. Это почти наверняка верно, если, например, ваш код Entity Framework запрашивает один экземпляр SQL Server. Это связано с тем, что основным преимуществом async на ASP.NET является масштабируемость, и если вам не нужна масштабируемость (части ASP.NET), вам не нужна асинхронность. См. Раздел "Не серебряная пуля" в моей статье MSDN на async ASP.NET.

Однако, я думаю, есть и аргумент в пользу "естественных API". Если операция естественно асинхронна (например, на основе ввода-вывода), то ее наиболее естественным API является асинхронный API. И наоборот, естественно синхронные операции (например, основанные на ЦП) наиболее естественно представлены в виде синхронных API. Естественный аргумент API является самым сильным для библиотек - если ваш уровень доступа к репозиторию/данным был его собственной dll, предназначенной для повторного использования в других (возможно, настольных или мобильных) приложениях, то это определенно будет асинхронным API. Но если (как это более вероятно), это специфично для этого приложения ASP.NET, которое не нужно масштабировать, тогда нет никакой особой необходимости сделать API асинхронным или синхронным.

Но есть хороший двухсторонний контраргумент в отношении опыта разработчиков. Многие разработчики вообще не знают своего пути async; может ли разработчик кода наверняка испортить его? Другим аргументом в пользу этого аргумента является то, что библиотеки и инструменты вокруг async все еще приближаются к скорости. Наиболее примечательным является отсутствие стека причинности, когда есть исключения для отслеживания (на боковой ноте я написал библиотеку , которая помогает в этом), Кроме того, части ASP.NET не являются async -совместимыми - в первую очередь, MVC-фильтрами и дочерними действиями (они фиксируют оба из них с ASP.NET vNext). А ASP.NET отличается поведением относительно тайм-аутов и прерываний потоков для асинхронных обработчиков - добавление еще немного к кривой обучения async.

Конечно, аргумент counter counter counter заключается в том, что надлежащий ответ разработчикам за разным временем должен обучать их, а не ограничивать доступными технологиями.

Короче:

  • Правильный способ сделать async - "полностью". Это особенно актуально для ASP.NET, и это вряд ли изменится в ближайшее время.
  • Является ли async подходящим или полезным, зависит от вас и вашего сценария приложения.