有效地使用 ASP.NET Web API 的异步/等待

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

工作流程:

UI 应用→ Web API 端点(控制器)→ Web API 服务层中的调用方法→调用另一个外部 Web 服务。(这里我们有 DB 交互,等等)

总监:

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的正确用法。请分享你的想法。

232991 次浏览

我会把你的服务层改成:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
return Task.Run(() =>
{
return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}
}

正如您所做的那样,您仍然在同步地运行 _service.Process调用,并且等待它的好处非常少或者没有。

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

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

由于没有可以等待的东西——没有对可以异步操作的阻塞 API 的调用——那么您正在设置结构来跟踪异步操作(这有开销) ,但是却没有利用这种功能。

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

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
using (db = myDBContext.Get()) {
var list = await db.Countries.Where(condition).ToListAsync();


return list;
}
}

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

等待往往是需要一直下去的东西: 很难改进到现有的系统中。

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

请记住,服务器端异步代码的主要好处是 可伸缩性。它不会神奇地让您的请求运行得更快。在我的 关于 async ASP.NET 的文章中,我讨论了几个“我应该使用 async吗”的注意事项。

我认为您的用例(调用其他 API)非常适合异步代码,只是要记住“异步”并不意味着“更快”。最好的方法是首先使你的 用户界面响应和异步; 这将使你的应用程序 感觉更快,即使它稍慢。

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

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

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

分配用于处理请求的线程将在 ReturnAllCountries()完成其工作时无所事事地等待。

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