C# NETMVC、WebApi和正确的异步方法

C# NETMVC、WebApi和正确的异步方法,c#,asp.net-mvc-4,task,async-await,asp.net-web-api2,C#,Asp.net Mvc 4,Task,Async Await,Asp.net Web Api2,我有一个多层web应用程序,最近我决定将我的服务层(本例中为WebApi)转换为异步处理 在这方面,我转换了所有WebApi方法来实现任务,在MVC部分,我实现了一个业务层,它调用WebApi 我的MVC控制器只是使用业务层类来获取视图数据 我对.NET4.5中基于任务的编程有点陌生,我想知道我的方法是正确的还是有缺陷的。在我的简单测试中,我看到响应时间的性能有所提高,但我不确定我所有的异步调用是安全的还是容易出错 代码示例: WebApi操作: [Route("category/withnew

我有一个多层web应用程序,最近我决定将我的服务层(本例中为WebApi)转换为异步处理

在这方面,我转换了所有WebApi方法来实现任务,在MVC部分,我实现了一个业务层,它调用WebApi

我的MVC控制器只是使用业务层类来获取视图数据

我对.NET4.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;
                    }
                });
        }
从同步方法调用异步方法是否正确

添加stargateheloper.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();
        }
    }
public异步任务InvokeAsync(字符串路径)
{
var httpResponse=await\u httpClient.GetAsync(\u baseUrl+path).ConfigureAwait(false);
httpResponse.EnsureAccessStatusCode();
使用(var responseStream=await-httpResponse.Content.ReadAsStreamAsync())
使用(var decompostream=new GZipStream(responseStream,CompressionMode.Decompress))
使用(var streamReader=newstreamreader(decompStream))
{
返回streamReader.ReadToEnd();
}
}

标准规则之一是不要在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;
  }
}
公共异步任务GetNewsCategoriesWithRecentNews(整数计数)
{
使用(var UoW=new UnitOfWork())
{
List returnList=新列表();
var activeAndVisibleCategories=UoW.CategoryRepository.GetActiveCategories()。其中(f=>f.IsVisible==true);
foreach(activeAndVisibleCategories中的var类别)
{
var dto=category.maptoto();
dto.RecentNews=await(从UoW.NewsRepository.GetByCategoryId(dto.Id)中的n开始)。其中(f=>f.IsVisible==true)。获取(计数)
选择n.maptodo(true)).ToListAsync();
返回列表。添加(dto);
}
退货清单;
}
}
你的服务助手大多看起来不错。提示:如果在一个方法中只使用一次
ConfigureAwait(false)
,则应该在该方法中的任何地方使用它

子操作是当前MVC的一个麻烦点;没有什么好办法可以做到这一点。ASP.NET vNext MVC具有异步兼容的“ViewComponents”,填补了这一空白。但今天,你必须从两个不完美的选项中选择一个:

  • 使用阻塞
    任务。结果
    并通过使用
    ConfigureAwait(false)
    避免死锁问题。这种方法的问题在于,如果您不小心忘记在需要使用它的任何地方使用
    ConfigureAwait(false)
    ,那么很容易再次导致死锁(在这种情况下,异步操作可以完美地工作,但子操作访问的同一代码会死锁,因此单元测试可能无法捕获它,代码覆盖率会产生误导)
  • 使用同步等价项复制子操作所需的所有服务方法。此方法还存在维护问题:服务逻辑重复

  • 标准规则之一是不要在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;
      }
    }
    
    公共异步任务GetNewsCategoriesWithRecentNews(整数计数)
    {
    使用(var UoW=new UnitOfWork())
    {
    List returnList=新列表();
    var activeAndVisibleCategories=UoW.CategoryRepository.GetActiveCategories()。其中(f=>f.IsVisible==true);
    foreach(activeAndVisibleCategories中的var类别)
    {
    var dto=category.maptoto();
    dto.RecentNews=await(从UoW.NewsRepository.GetByCategoryId(dto.Id)中的n开始)。其中(f=>f.IsVisible==true)。获取(计数)
    选择n.maptodo(true)).ToListAsync();
    返回列表。添加(dto);
    }
    退货清单;
    }
    }
    
    您的服务助手大多看起来不错。提示:如果您在一个方法中使用
    ConfigureAwait(false)
    一次,那么应该在该方法中的任何地方使用它

    子操作是当前MVC的一个麻烦点;没有好的方法来执行这些操作。ASP.NET vNext MVC具有异步兼容的“ViewComponents”,这填补了这一空白。但今天,您必须从两个不完美的选项中选择一个:

  • 使用阻塞
    Task.Result
    并通过使用
    ConfigureAwait(false)
    避免死锁问题。这种方法的问题是,如果您不小心忘记在需要使用它的任何地方使用
    ConfigureAwait(false)
    ,则很容易再次导致死锁(在这种情况下,异步操作可以完美地工作,但子操作访问的同一代码会死锁,因此单元测试可能无法捕获它,代码覆盖率会产生误导)
  • 使用同步等价项复制子操作所需的所有服务方法。此方法还存在维护问题:服务逻辑重复

  • 非常感谢。这是非常有用的。我正在使用ConfigureWait(false)从服务帮助器到HttpClient包装器,我是否也应该在控制器调用帮助器时使用它?此外,不幸的是,此项目仍在使用EF 5,因此异步数据层不受atm支持。我将很快升级数据层,但在此之前,Task.Run必须工作。它对性能有太大的影响,还是只是一个“规则”在基于任务的编程中,
    ConfigureAwait(false)
    的指导原则是在任何地方都可以使用它。这通常是服务/域层,但通常不是控制器方法(控制器助手方法,如
    View
    生成响应,因此它们需要请求/响应上下文)在ASP.NET中,
    Task.Run
    是有害的;它会
    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();
            }
        }
    
    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;
      }
    }