У меня многоуровневое веб-приложение, и в последнее время я решил преобразовать свой сервисный уровень (WebApi в этом случае) в асинхронную обработку.
В связи с этим, я переработал все свои методы WebApi для реализации задач и в части MVC, я реализовал бизнес-уровень, который вызывает вызовы в WebApi.
Мои MVC-контроллеры просто используют классы бизнес-уровня для получения данных просмотра.
Я как бы новичок в этом программировании на основе задач в.NET 4.5 и хочу знать, правильный ли мой подход или недостаток. В моих простых тестах я видел увеличение производительности во время ответа, но я не уверен, что все мои асинхронные вызовы безопасны или подвержены ошибкам.
Образцы кода:
Действие WebApi:
[Route("category/withnews/{count:int=5}")]
public async Task<IEnumerable<NewsCategoryDto>> GetNewsCategoriesWithRecentNews(int count)
{
return await Task.Run<IEnumerable<NewsCategoryDto>>(() =>
{
using (var UoW = new UnitOfWork())
{
List<NewsCategoryDto> returnList = new List<NewsCategoryDto>();
var activeAndVisibleCategories = UoW.CategoryRepository.GetActiveCategories().Where(f => f.IsVisible == true);
foreach (var category in activeAndVisibleCategories)
{
var dto = category.MapToDto();
dto.RecentNews = (from n in UoW.NewsRepository.GetByCategoryId(dto.Id).Where(f => f.IsVisible == true).Take(count)
select n.MapToDto(true)).ToList();
returnList.Add(dto);
}
return returnList;
}
});
}
Метод бизнес-класса для вызова этого api (класс NewsService в приложении MVC).
public async Task<IndexViewModel> GetIndexViewModel()
{
var model = new IndexViewModel();
using (var stargate = new StargateHelper())
{
string categoriesWithNews = await stargate.InvokeAsync("news/category/withnews/" + model.PreviewNewsMaxCount).ConfigureAwait(false);
var objectData = JsonConvert.DeserializeObject<List<NewsCategoryDto>>(categoriesWithNews);
model.NewsCategories = objectData;
}
return model;
}
Действие MVC-контроллера для просмотра ViewModel
public async Task<ActionResult> Index()
{
_service.ActiveMenuItem = "";
var viewModel = await _service.GetIndexViewModel();
return View(viewModel);
}
Однако некоторые из действий Controller являются PartialViewResults, и поскольку они являются ChildActions, я не могу преобразовать их в async-действия, такие как действие индекса. Что я делаю в этом случае:
var viewModel = _service.GetGalleryWidgetViewModel().Result;
return PartialView(viewModel);
Это правильный способ вызова асинхронного метода из синхронного метода?
Добавление StargateHelper.InvokeAsync для справки тоже:
public async Task<string> InvokeAsync(string path)
{
var httpResponse = await _httpClient.GetAsync(_baseUrl + path).ConfigureAwait(false);
httpResponse.EnsureSuccessStatusCode();
using (var responseStream = await httpResponse.Content.ReadAsStreamAsync())
using (var decompStream = new GZipStream(responseStream, CompressionMode.Decompress))
using (var streamReader = new StreamReader(decompStream))
{
return streamReader.ReadToEnd();
}
}
Одно из стандартных правил - не использовать Task.Run
на ASP.NET. Вместо этого вы должны использовать естественно-асинхронные API.
Например, в вашем WebAPI, если вы используете EF6:
public async Task<IEnumerable<NewsCategoryDto>> GetNewsCategoriesWithRecentNews(int count)
{
using (var UoW = new UnitOfWork())
{
List<NewsCategoryDto> returnList = new List<NewsCategoryDto>();
var activeAndVisibleCategories = UoW.CategoryRepository.GetActiveCategories().Where(f => f.IsVisible == true);
foreach (var category in activeAndVisibleCategories)
{
var dto = category.MapToDto();
dto.RecentNews = await (from n in UoW.NewsRepository.GetByCategoryId(dto.Id).Where(f => f.IsVisible == true).Take(count)
select n.MapToDto(true)).ToListAsync();
returnList.Add(dto);
}
return returnList;
}
}
Ваши сервисные помощники в основном выглядят хорошо. Совет. Если вы используете ConfigureAwait(false)
один раз в методе, его следует использовать везде в этом методе.
Действия ребенка - это проблема с текущим MVC; у них нет хорошего способа сделать это. ASP.NET vNext MVC имеет асинхронные "ViewComponents", которые заполняют эту нишу. Но на сегодняшний день вам нужно выбрать один из двух несовершенных вариантов:
Task.Result
и избегайте проблемы с блокировкой, используя ConfigureAwait(false)
. Проблема с этим подходом заключается в том, что если вы случайно забыли использовать ConfigureAwait(false)
всюду, он должен быть использован, тогда вы можете легко снова завести тупик (и это будет то, что действия async будут работать отлично, но тот же код, к которому обращается дочернее действие, будет заторможен, поэтому юнит-тесты могут не поймать его, а покрытие кода вводит в заблуждение).
ConfigureAwait(false)
- использовать его везде, где только можно. Обычно это уровень службы / домена, но обычно это не методы контроллера (вспомогательные методы контроллера, такие какView
создают ответ, поэтому им необходим контекст запроса / ответа).Task.Run
наносит ущерб ASP.NET; он удаляет все преимущества, которые вы получаете отasync
а затем немного снижает производительность. Однако, если это просто временно, и вы определенно переходите на EF6 (удаляя все экземплярыTask.Run
в то время), то я полагаю, что это приемлемо. :)Task.Run
прекрасно работает в приложениях с пользовательским интерфейсом, но не в ASP.NET.