在 webapi 中正确使用 await async
Posted
技术标签:
【中文标题】在 webapi 中正确使用 await async【英文标题】:Correct usage of await async in webapi 【发布时间】:2013-08-14 14:47:35 【问题描述】:我有一个 WebApi,它为每个传入请求调用 2 个单独的 Web 服务,执行一些后期处理并返回结果。
第一个 webservice 调用在本地缓存 1 小时,其中的数据决定了对第二个 webservice 的查询。对每个传入请求调用第二个 Web 服务。在发出第二个请求后,每个结果都会使用业务逻辑进行处理并返回给客户端响应。
对第二个 Web 服务的调用不能是异步的,因为它使用了不允许 await 关键字的第 3 方 dll。我所做的是将第二个 web 服务调用和后处理封装到一个异步函数中,该函数从控制器调用。
// /api/controller/news?key=a&state=b
public async Task<HttpResponseMessage> GetNews(string key, string state)
// call to first webservice if not in cache
if (JsonConfig != null && JsonConfig.Configuration.NewsQuery.ContainsKey(key))
var results = await SearchProxyProvider.Search(filters.All, filters.Any, filters.None, filters.Sort, 100, 0, true, state, true);
int totalCount = results.TotalCount;
return Request.CreateResponse(HttpStatusCode.OK, results);
// Helper class method
public async Task<ItemCollection<Item>> Search(List<FieldValuePair> allFilters, List<FieldValuePair> anyFilters, List<FieldValuePair> noneFilters, SortedFieldDictionary sortBy, int pageSize = 100, int pageNumber = 0, bool exact = true, string stateFilter = null, bool getAllResults = true)
// call to 2nd api
search = SomeApi.Search(allFilters, anyFilters, noneFilters, pageSize, pageNumber, exact,
sortBy, null, WebApiConstant.Settings.CustomFields, true);
// post processing on search results
return search;
因为对第一个 Web 服务的调用被缓存在本地,所以我并不认为将其设为异步有很大的好处。
我只是想看看这种方法是完全错误还是正确。
【问题讨论】:
您预计平均每小时会调用多少次 Web api? 查看 1000 个并发用户。会有多台服务器对webapi进行负载均衡,2-3。预计初始应用下载量为 40 万用户。 【参考方案1】:第一个 webservice 调用在本地缓存 1 小时,其中的数据决定了对第二个 webservice 的查询。
您可以使用AsyncLazy<T>
(例如from my blog)做一些技巧,然后将其缓存。这为您的请求提供了一种(异步)等待刷新的方法,并且当您需要刷新数据时,无论同时请求的数量如何,您都不会多次点击 Service1。
对第二个 web 服务的调用不能是异步的,因为它使用了一个不允许 await 关键字的 3rd 方 dll。
这真是太糟糕了。一定要依靠他们来解决它。 :)
我所做的是,将第二个 Web 服务调用和后处理封装到一个异步函数中,该函数从控制器调用。
这没有任何意义。编译器会警告您,您的“异步”方法实际上是同步的。
如果是同步的,那么就同步调用它。在服务器端,用Task.Run
或类似的东西包装它是没有意义的。
我有一些slides available from an "Async on the Server" talk 我在星期一给ThatConference,你可能会觉得有帮助。 (他们有关于如何处理异步请求以及为什么“假异步”方法对服务器端没有帮助的动画)。
【讨论】:
因此,如果我的 Web 调用都不能被异步调用,但我有一个方法可以执行长时间运行的任务,我怎样才能使它异步呢? (服务器端 WebAPI) @mickyjtwin:你不应该。您可以将其包装在Task.Run
中,但这不会为您带来任何好处。 WebAPI 中async
的全部意义在于减少线程数,因此您根本不想将工作排队到线程池中。如果这个解释不清楚,那么想想这个问题的答案:为什么我想要让它异步?
你能告诉我一些关于这方面的详细书籍的方向吗?我真的很想深入了解正在发生的事情,并在更深入的层面上理解这一点。
@StephenCleary Stephen,为 GetNews 方法保留异步并将同步 SomeApi.Search 代码包装到 Task.Run 中没有好处吗?假设 SomeApi.Search 无法修改并保持不变,您是否建议从这两个功能中删除异步键盘?谢谢。
@mickyjtwin:AFAIK 没有专门针对服务器端async
的书籍。有一个很棒的video on Channel9,但除此之外,我所知道的最好的资源是我自己的博客和幻灯片(希望我会在几周内发布视频)。【参考方案2】:
要使函数 Search 真正异步调用 SomeApi.Search 可以包装到一个单独的任务中,然后等待。还可以考虑将第一个函数中的代码包装到一个任务中,您的负载估计缓存验证也可能是一个瓶颈。
// call to 2nd api
var search = await Task.Factory.StartNew(()=> return Some Api.Search(allFilters, anyFilters, noneFilters, pageSize, pageNumber, exact,
sortBy, null, WebApiConstant.Settings.CustomFields, true););
【讨论】:
实际上,那是“假异步”,而不是“真正异步”,您肯定不想在服务器端(WebAPI)这样做。顺便说一句,Task.Run
对于async
代码比StartNew
更好。
对于服务器端 webapi,您将什么归类为真正的异步?
我使用术语“真正异步”来表示不将工作排队到线程池的异步方法。在服务器端,async
的全部意义在于释放工作线程,如果您通过将工作排队到线程池来释放工作线程(由另一个工作线程执行线程),你一无所获。 OTOH,这对于 client 上的async
来说是一种有用的技术,因为关键是要释放 UI 线程。因此,您可以将工作排队到线程池,这很好,因为它不是在 UI 线程上完成的。
@StephenCleary 感谢您的反馈。在评论“假异步”之前,我将查看您提到的幻灯片。关于您对 Task.Run 的赞扬,我认为如果您提到 Task.Run 只是 Task.Factory.StartNew 的一个较短的替代方案会更好。 “更好”这个词太笼统了。
@AlexS:我通常使用的措辞是“Task.Run
比 TaskFactory.StartNew
更适合 async
代码。Here's why。”但“更好”并不是错误的。 StartNew
的问题在于它很容易被错误地使用,例如,忘记了 TaskScheduler
参数(应该总是指定 - 一些细节在 Stephen Toub 的博客文章中)。跨度>
以上是关于在 webapi 中正确使用 await async的主要内容,如果未能解决你的问题,请参考以下文章
通过 ASP.NET Web API 有效地使用 async/await
使用 babel js 将 async/await 编译为 es5