Где правильно использовать async/await в WebAPI

Roman Marusyk

Мы никогда не использовали async/await (или что-то подобное) в нашем небольшом проекте. Но сейчас проект растет, и мы решили добавить асинхронность.

Проект имеет следующую архитектуру:

  • Domain (репозиторий, EF)
  • UnitTests
  • Service (новый, тут планируется перенести бизнес логику с контроллеров)
  • WebUI

Мой вопрос, в каком слое мы должны реализовать асинхронность? Или мы должны сделать это на нескольких слоях? Пожалуйста, подскажите мне, где реализация будет наиболее подходящей?

Например у меня есть метод в контроллере

public virtual HttpResponseMessage Get()
{ var entity = Repository.GetAll(); if (entity != null && entity.Any()) { return Request.CreateResponse(HttpStatusCode.OK, entity); } var message = $"{GenericTypeName}: No content"; return ErrorMsg(HttpStatusCode.NoContent, message);
}

Как мне сделать его асинхронным?

2 ответа

Roman Marusyk

class Repository
{ public Task<entity> GetAllAsync(CancellationToken cancellationToken) { return Task.Run<entity>(() => { // тут что-то ресурсоёмкое вычисляем return entity; // возвращаем результат }); }
}
</entity></entity>

Второй пример, то же самое только с async await

class Repository
{ public async Task<entity> GetAllAsync(CancellationToken cancellationToken) { // вернёт управление в вызывающий код await Task.Yield(); // тут что-то ресурсоёмкое вычисляем return entity; // возвращаем результат }
}
</entity>

Третий пример, если нужно перед возвратом данных вычислить, что-то параллельно

class Repository
{ public async Task<entity> GetAllAsync(CancellationToken cancellationToken) { var tasks = new List<task>(); tasks.Add(Task.Run<entity>(() => { // тут что-то ресурсоёмкое вычисляем return entity; // возвращаем результат })); tasks.Add(Task.Run<entity>(() => { // тут что-то ресурсоёмкое вычисляем return entity; // возвращаем результат })); tasks.Add(Task.Run<entity>(() => { // тут что-то ресурсоёмкое вычисляем return entity; // возвращаем результат })); // дожидаемся окончания всех задач await Task.WhenAll(tasks); // дальше пробегаемся по списку задач, получаем результат работы каждой и что-то с ним делаем. И возвращаем уже вычисленный нами результат. }
}
</entity></entity></entity></task></entity>

Конечно в реальном боевом приложении код будет немного другой и зависеть от конкретной задачи но принцип будет один и тот же.

Обращаю внимание, что async/await это синтаксическая обёртка над Task.Run с некоторыми отличиями. А отличия в обработке исключений в процессе выполнения задачи.

Но прежде чем писать асинхронный код обязательно прочитайте про async/await И task иначе будет множество ошибок. Особенно внимательно нужно смотреть обработку исключений.


Roman Marusyk

Асинхронность распространяется на все слои. От нижнего к верхнему. В вашем случае, если Domain асинхронный то и WebUI будет асинхронным. Не забывайте, что асинхронность нужна только для операций с периферийными устройствами (диск, сеть...) для того, что бы при ожидании ответа не простаивал рабочий поток.

class Repository
{ public Task<entity> GetAllAsync(CancellationToken cancellationToken) { using (var db = new DataContext()) { return db.EntitySet.ToListAsync(); } }
}
public virtual async Task<ihttpactionresult> Get(CancellationToken cancellationToken)
{ var entity = await Repository.GetAllAsync(cancellationToken); if (!entity.Any()) { var message = $"{GenericTypeName}: No content"; return Content(HttpStatusCode.NoContent, message); } return Ok(entity);
}
</ihttpactionresult></entity>

Обратите внимание http status code 204 - No content (это не ошибочный статус код)

Для справки: https://ru.wikipedia.org/wiki/Список_кодов_состояния_HTTP

И вообще вам прежде нужно почитать про Task и async await. Task.Run используется для cpu bound operations (Вычислительных операций процессора) async await для ожидания результата этих операций в вызывающем коде или ожидания операций I/O bound operations.

То что касается entity framework то в 6 версии есть асинхронные методы их названия заканчиваются на Async, например .ToListAsync().

licensed under cc by-sa 3.0 with attribution.