【问题标题】:Asp.Net MVC, WebApi and Correct Asynchronous ApproachAsp.Net MVC、WebApi 和正确的异步方法
【发布时间】:2014-07-07 12:54:18
【问题描述】:

我有一个多层 Web 应用程序,最近我决定将我的服务层(在本例中为 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的业务类方法(MVC应用中的NewsService类。)

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 Controller Action 获取 ViewModel

public async Task<ActionResult> Index()
        {
            _service.ActiveMenuItem = "";

            var viewModel = await _service.GetIndexViewModel();
            return View(viewModel);
        }

但是,某些控制器操作是 PartialViewResults 并且因为它们是 ChildActions,所以我无法将它们转换为像 Index 操作这样的异步操作。在这种情况下我要做的是:

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();
        }
    }

【问题讨论】:

    标签: c# asp.net-mvc-4 task async-await asp.net-web-api2


    【解决方案1】:

    标准规则之一是不要在 ASP.NET 上使用Task.Run。相反,您应该使用自然异步 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),那么您很容易再次导致死锁(这将是异步操作可以完美运行但同样的事情子操作访问的代码会死锁,因此单元测试可能无法捕获它并且代码覆盖率会产生误导)。
    2. 使用同步等效项复制子操作所需的所有服务方法。这种方式也存在维护问题:服务逻辑重复。

    【讨论】:

    • 非常感谢。这是非常有用的。我一直在使用从服务助手到 HttpClient 包装器的 ConfigureAwait(false),我是否也应该在对助手的控制器调用中使用它?此外,不幸的是,该项目仍在使用 EF 5,因此异步数据层有点不支持 atm。我会尽快升级数据层,但在那之前 Task.Run 必须工作。它对性能的影响太大还是只是基于任务的编程中的“规则”?
    • ConfigureAwait(false) 的指导方针是在任何地方使用它。这通常是服务/域层,但通常不是控制器方法(控制器辅助方法,如View 构建响应,因此它们需要请求/响应上下文)。 Task.Run 在 ASP.NET 中是有害的;它消除了您从async 获得的所有好处,然后进一步损害了性能。但是,如果它只是暂时的,并且您肯定要迁移到 EF6(当时删除 Task.Run 的所有实例),那么我认为这是可以接受的。 :) Task.Run 在 UI 应用程序中很好,只是在 ASP.NET 上不行。
    • 啊,我明白了,现在我对如何以及在何处使用 ConfigureAwait(false) 更有信心 :) 一旦迁移到 EF6,我将摆脱所有 Task.Run 调用。谢谢斯蒂芬。
    猜你喜欢
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 2017-04-23
    • 2015-05-30
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多