Asp.Net MVC, WebApi и правильный асинхронный подход

1

У меня многоуровневое веб-приложение, и в последнее время я решил преобразовать свой сервисный уровень (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();
        }
    }
Теги:
async-await
task
asp.net-web-api2
asp.net-mvc-4

1 ответ

5
Лучший ответ

Одно из стандартных правил - не использовать 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", которые заполняют эту нишу. Но на сегодняшний день вам нужно выбрать один из двух несовершенных вариантов:

  1. Используйте блокировку Task.Result и избегайте проблемы с блокировкой, используя ConfigureAwait(false). Проблема с этим подходом заключается в том, что если вы случайно забыли использовать ConfigureAwait(false) всюду, он должен быть использован, тогда вы можете легко снова завести тупик (и это будет то, что действия async будут работать отлично, но тот же код, к которому обращается дочернее действие, будет заторможен, поэтому юнит-тесты могут не поймать его, а покрытие кода вводит в заблуждение).
  2. Дублируйте все методы обслуживания, требуемые дочерними действиями с синхронными эквивалентами. Этот подход также имеет проблему обслуживания: дублируется служебная логика.
  • 0
    Большое спасибо. Это очень информативно. Я использую ConfigureAwait (false) на всем пути от сервисных помощников до обертки HttpClient. Должен ли я использовать его при вызовах контроллера для помощников? Кроме того, к сожалению, в этом проекте все еще используется EF 5, поэтому асинхронный уровень данных не поддерживается. Я скоро обновлю слой данных, но до тех пор Task.Run должен работать. Это слишком сильно влияет на производительность или это просто «правило» в программировании на основе задач?
  • 0
    Рекомендация для ConfigureAwait(false) - использовать его везде, где только можно. Обычно это уровень службы / домена, но обычно это не методы контроллера (вспомогательные методы контроллера, такие как View создают ответ, поэтому им необходим контекст запроса / ответа). Task.Run наносит ущерб ASP.NET; он удаляет все преимущества, которые вы получаете от async а затем немного снижает производительность. Однако, если это просто временно, и вы определенно переходите на EF6 (удаляя все экземпляры Task.Run в то время), то я полагаю, что это приемлемо. :) Task.Run прекрасно работает в приложениях с пользовательским интерфейсом, но не в ASP.NET.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню