在 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&lt;T&gt;(例如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.RunTaskFactory.StartNew 更适合 async 代码。Here's why。”但“更好”并不是错误的。 StartNew 的问题在于它很容易被错误地使用,例如,忘记了 TaskScheduler 参数(应该总是指定 - 一些细节在 Stephen Toub 的博客文章中)。跨度>

以上是关于在 webapi 中正确使用 await async的主要内容,如果未能解决你的问题,请参考以下文章

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

异步编程初探async和await

使用 Async /Await 调用 Web API

使用 babel js 将 async/await 编译为 es5

Angular 数据绑定不适用于 async/await,但它适用于 Promise

教你正确打开async/await关键字的使用