将非异步方法(进行网络调用)包装到异步中

Posted

技术标签:

【中文标题】将非异步方法(进行网络调用)包装到异步中【英文标题】:Wrapping a non async-method (which does web calls) into async 【发布时间】:2017-07-22 05:13:41 【问题描述】:

我知道您应该只对非“CPU 密集型”的东西使用异步,例如文件写入、网络调用等,因此我也知道将每个方法都包装成 Task.Run 或类似的东西是没有意义的。

但是,当我知道一个方法进行网络调用,但它不提供异步接口时,我应该怎么做。在这种情况下值得包装吗?

具体例子:

我在我的 WebApi 应用程序(服务器)中使用 CSOM(客户端 SharePoint 对象模型)并且想要获取 SharePoint 列表。

这通常是这样完成的:

[HttpGet]
[Route("foo/webUrl")]
public int GetNumberOfLists(string webUrl)

    using (ClientContext context = new ClientContext(webUrl))
    
        Web web = context.Web; 
        context.Load(web.Lists); 
        context.ExecuteQuery(); 

        return web.Lists.Count;
    

我想过把它改成这样:

[HttpGet]
[Route("foo/webUrl")]
public async Task<int> GetNumberOfLists(string webUrl)

    using (ClientContext context = new ClientContext(webUrl))
    
        Web web = context.Web; 
        context.Load(web.Lists); 
        await Task.Run(() => clientContext.ExecuteQuery());

        return web.Lists.Count;
    

这有意义吗?它有帮助吗?据我了解,我只是创建/需要一个新线程来执行查询(“开销”),但至少请求线程将空闲/准备好接受另一个请求(这很好)。

但这值得吗?应该这样做吗?

如果是这样: 微软没有提供开箱即用的“异步”方法,或者他们只是不关心它,这难道不奇怪吗?

编辑: 按照评论中的建议更新为使用Task.Run

【问题讨论】:

不要使用Task.Factory.StartNew,使用Task.Run。至于调用SharePoint,上下文有ExecuteQueryAsync方法,可以适应返回一个Task 我正在使用的 CSOM 库中没有 ExecuteQueryAsync.... 我更改的另一件事。 自 2010 年以来,CSOM 有一个异步 ExecuteQueryAsync 方法。您使用的是哪个 SharePoint 版本,2010、2013 还是在线?每个都有不同的 CSOM 包 您的意思是 docs 提到的方法 - 尽管我看到引用的 dll 仅适用于 Silverlight。看起来他们添加了 only there 方法,但文档页面不完整。我忘记了 API 和文档是多么直观。无论如何,CSOM 最终只是一组 REST 调用。如果您关心可伸缩性,您可以随时使用 HttpClient 或 OData。 PS - 现在你会明白为什么有些人称自己为 SharePoint 难民了 【参考方案1】:

但是,当我知道一个方法进行网络调用,但它不提供异步接口时,我该怎么办。

不幸的是,仍然有些普遍。随着不同的库更新其 API,它们最终会赶上来。

在这种情况下值得包装吗?

是的,如果您正在处理 UI 线程。否则,不行。

具体示例...在我的 WebApi 应用程序(服务器)中

那么,不,你不想换行Task.Run。如我的article on async ASP.NET 所述:

您可以通过等待 Task.Run 来启动一些后台工作,但这样做没有任何意义。事实上,这实际上会通过干扰 ASP.NET 线程池试探法而损害您的可伸缩性...作为一般规则,不要将工作排队到 ASP.NET 上的线程池中。

在 ASP.NET 上使用 Task.Run 包装:

两次干扰 ASP.NET 线程池试探法(通过现在获取一个线程,然后稍后释放它)。 增加开销(代码必须切换线程)。 不释放线程(用于此请求的线程总数几乎等于仅调用同步版本)。

据我了解,我只是创建/需要一个新线程来执行查询(“开销”),但至少请求线程将空闲/准备好接受另一个请求(这很好)。

是的,但你所做的只是跳线程,没有任何好处。用于阻塞查询结果的线程比 ASP.NET 用于处理请求的线程少一个,因此通过消耗另一个线程来释放一个线程并不是一个好的权衡。

微软没有提供开箱即用的“异步”方法,或者他们根本不关心它,这不是很奇怪吗?

一些“较旧”的 MS API 还没有开始添加 async 版本。他们当然应该,但开发人员的时间是有限的资源。

【讨论】:

完美的答案,正是我“正在寻找的”——谢谢!【参考方案2】:

这是我对您问题的个人看法,对我来说,上述方法不是必需的。当我们在 IIS 中托管您的 API 时,服务器会从它在服务器中的线程池中分配一个线程。 IIS 还具有 maxConcurrentRequestsPerCPU maxConcurrentThreadsPerCPU 的设置。您可以设置这些值来处理请求,而不是自己处理请求。

【讨论】:

以上是关于将非异步方法(进行网络调用)包装到异步中的主要内容,如果未能解决你的问题,请参考以下文章

使用 XCTest 进行异步方法测试

在 iOS 中等待多个网络异步调用

如何使用回调在任何函数中调用异步等待方法

如何用模拟和预定义的答案替换异步调用?

使用 RxSwift 同步异步网络调用

将同步 API 包装到 Async 方法中