通过 ASP.NET Web API 有效地使用 async/await

Posted

技术标签:

【中文标题】通过 ASP.NET Web API 有效地使用 async/await【英文标题】:Effectively use async/await with ASP.NET Web API 【发布时间】:2015-09-20 00:17:55 【问题描述】:

我正在尝试在我的 Web API 项目中使用 ASP.NET 的 async/await 功能。我不太确定它是否会对我的 Web API 服务的性能产生任何影响。请在下面找到我的应用程序的工作流程和示例代码。

工作流程:

UI 应用 → Web API 端点(控制器) → 在 Web API 服务层调用方法 → 调用另一个外部 Web 服务。 (这里有数据库交互等)

控制器:

public async Task<IHttpActionResult> GetCountries()

    var allCountrys = await CountryDataService.ReturnAllCountries();

    if (allCountrys.Success)
    
        return Ok(allCountrys.Domain);
    

    return InternalServerError();

服务层:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()

    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

    return Task.FromResult(response);

我测试了上面的代码并且正在运行。但我不确定async/await的正确用法是否正确。请分享您的想法。

【问题讨论】:

相关帖子 - best practice for using async await in webapi 【参考方案1】:

您没有有效地利用 async / await 因为请求线程将在执行 同步 方法时被阻塞 ReturnAllCountries()

分配给处理请求的线程将在ReturnAllCountries() 工作时空闲等待。

如果您可以将ReturnAllCountries() 实现为异步的,那么您将看到可扩展性优势。这是因为线程可以释放回 .NET 线程池以处理另一个请求,而 ReturnAllCountries() 正在执行。通过更有效地利用线程,这将使您的服务具有更高的吞吐量。

【讨论】:

这不是真的。套接字已连接,但处理请求的线程可以执行其他操作,例如在等待服务调用时处理不同的请求。【参考方案2】:

我不太确定这是否会对我的 API 性能产生任何影响。

请记住,服务器端异步代码的主要好处是可扩展性。它不会神奇地使您的请求运行得更快。我在article on async ASP.NET 中介绍了几个“我应该使用async”的注意事项。

我认为您的用例(调用其他 API)非常适合异步代码,请记住“异步”并不意味着“更快”。最好的方法是首先让您的 UI 响应式和异步;这将使您的应用感觉更快,即使它稍微慢一些。

就代码而言,这不是异步的:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()

  var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
  return Task.FromResult(response);

您需要一个真正的异步实现来获得async 的可扩展性优势:

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()

  return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

或者(如果你在这个方法中的逻辑真的只是一个传递):

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()

  return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

请注意,像这样从“由内而外”而不是“由外而内”工作更容易。换句话说,不要从异步控制器操作开始,然后强制下游方法异步。相反,识别自然异步操作(调用外部 API、数据库查询等),并首先在 最低 级别使这些异步操作 (Service.ProcessAsync)。然后让async 慢慢上升,最后一步使您的控制器操作异步。

在这种情况下,在任何情况下都不应使用Task.Run

【讨论】:

你能解释一下为什么你不应该在这里使用Task.Run吗? @Maarten:我在the article I linked to 中(简要地)解释了它。我会更详细地介绍on my blog。 我读过你的文章斯蒂芬,似乎避免提及一些事情。当 ASP.NET 请求到达并开始时,它正在线程池线程上运行,这很好。但是,如果它变为异步,那么是的,它会启动异步处理,并且该线程会立即返回到池中。但是异步方法中执行的工作本身会在线程池线程上执行! @Hugh: Asynchronous I/O operations do not block a thread.【参考方案3】:

它是正确的,但也许没有用。

由于没有什么可以等待 - 没有调用可以异步操作的阻塞 API - 那么您正在设置结构来跟踪异步操作(有开销),但随后不使用该功能。

例如,如果服务层使用支持异步调用的实体框架执行数据库操作:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()

    using (db = myDBContext.Get()) 
      var list = await db.Countries.Where(condition).ToListAsync();

       return list;
    

您将允许工作线程在查询数据库时执行其他操作(从而能够处理另一个请求)。

Await 往往是需要一直向下的东西:很难改造到现有系统中。

【讨论】:

【参考方案4】:

我会将您的服务层更改为:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()

    return Task.Run(() =>
    
        return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
          

正如你所拥有的那样,你仍然在同步运行你的_service.Process 调用,并且从等待它中获得的好处很少或没有。

使用这种方法,您可以将可能很慢的调用包装在 Task 中,启动它,然后将其返回以等待。现在您可以等待Task

【讨论】:

根据博客link,我可以看到即使我们使用 Task.Run 方法仍然同步运行? 嗯。上面的代码和那个链接有很多不同之处。您的 Service 不是异步方法,并且不会在 Service 调用本身中等待。我可以理解他的观点,但我认为它不适用于这里。 Task.Run 在 ASP.NET 上应避免使用。这种方法将消除 async/await 的所有好处,并且实际上在负载下的性能比仅同步调用更差。

以上是关于通过 ASP.NET Web API 有效地使用 async/await的主要内容,如果未能解决你的问题,请参考以下文章

使用 ADFS 对 ASP.NET Web Api 进行身份验证

csharp 在ASP.NET Web Api有效负载中添加资源链接

Asp.net MVC 与 Asp.net Web API 区别

ASP.net Web API RESTful Web 服务 + 基本身份验证

如何在 ASP Net Core Web API 上正确地将列表转换为 IQueryable

如何使用 HTTP 连接通过托管在 IIS 上的 ASP.NET 成员身份保护 ASP.NET Web API [重复]