是否使用TPL还是async/await

3

现有第三方Rest API可用,它将接受一组输入并返回相应的输出。(可以将其视为必应的地理编码服务,该服务将接受地址并返回位置详细信息)

我的需求是为单个asp.net请求调用此API多次(例如500-1000次),每次调用可能需要接近500毫秒才能返回。

我能想到三种方法来执行此操作。需要您的意见,以速度为标准选择最佳方法。

1. 在for循环中使用Http请求

编写一个简单的for循环,对于每个输入调用REST API并将输出添加到结果中。这可能是最慢的方法。但没有线程或上下文切换的开销。

2. 使用async和await

使用async和await机制调用REST Api。这样可以提高效率,因为线程在等待REST调用返回时可以继续执行其他活动。我面临的问题是,按照建议,我应该一直使用await到最顶层的调用者,但在我的情况下不可能做到。不遵循这个建议可能会导致asp.net中的死锁,如此处所述http://msdn.microsoft.com/en-us/magazine/jj991977.aspx 3.使用Task Parallel Library
使用Parallel.ForEach并使用同步API并行调用服务器,并使用ConcurrentDictionary保存结果。但可能会导致线程开销。
另外,请让我知道是否有其他更好的处理方法。我明白人们可能建议跟踪每种方法的性能,但想了解人们在解决这个问题之前是如何解决的。

1
不要在Http请求中收集结果并将其发送回客户端,为什么不让客户端发起多个请求并将它们转发到服务器,让客户端像JavaScript一样管理异步逻辑呢? - Akash Kava
@AkashKava 客户通过点击按钮来完成输入。我需要调用服务API并将每个条目的结果存储在数据库中。要求JS执行此操作不是高效的方法。 - Ramesh
1
为什么不能一路使用异步(async)呢? - svick
@svick - 我目前正在考虑修改整个控制器以使其异步化。 - Ramesh
@Ramesh,这可能是你正在寻找的东西:https://dev59.com/e3zaa4cB1Zd3GeqPVuTX#22002868 - noseratio - open to work
显示剩余4条评论
2个回答

4
最好的解决方案是使用asyncawait,但在这种情况下,你必须将其async一直进行到控制器操作。

循环保持了所有顺序和同步,因此它肯定是最慢的解决方案。Parallel将会阻止多个线程每个请求,这将对可伸缩性产生负面影响。

由于该操作是基于I/O的(调用REST API),所以async是最自然的选择,并应提供这些选项中最佳的整体系统性能。


2
首先,我认为值得考虑一些你在问题中没有提到的问题:
  • 500-1000个API调用听起来很多。难道没有避免这种情况的方法吗?API是否有某种批量查询功能?或者您不能下载他们的数据库并在本地查询吗?(像维基媒体或Stack Exchange这样更开放的组织通常支持此功能,而像Microsoft或Google这样更封闭的组织通常不支持。)

    如果这些选项不可用,则至少考虑某种缓存,如果对您有意义的话。

  • ASP.NET中同时允许向同一服务器发送的并发请求数量默认为10。如果您想进行更多的并发请求,则需要设置ServicePointManager.DefaultConnectionLimit

  • 进行这么多请求可能被服务提供商视为滥用,并可能导致您的IP被阻止。确保提供商对这种使用方式表示赞同。

现在回答您的问题:我认为最好的选择是使用async-await,即使你不能完全使用它。你可以通过在每个await使用ConfigureAwait(false)(这是正确的解决方案),或者使用类似于Task.Run(() => /* your async code here */).Wait()来逃避ASP.NET上下文(这是简单的解决方案)来避免死锁。
Parallel.ForEach()这样的东西不是很好,因为它会不必要地浪费ThreadPool线程。
如果您选择使用async,您应该考虑限流。一个简单的实现方式就是使用SemaphoreSlim

感谢 @scivk - 没有批量查询/数据库访问。肯定会考虑缓存值。对于同一服务器的并发连接,这对我来说是新的。我会更详细地了解一下。我相信第三方已经将我们服务器的IP地址列入白名单。非常有启发性。谢谢 - Ramesh
请问您能否解释一下在异步情况下需要进行节流的原因? - Ramesh
设置 ServicePointManager.DefaultConnectionLimit 会影响整个应用程序。因此,如果您想在应用程序的不同部分使用不同数量的并发连接,则限流可能是正确的方法。 - svick
我已将所有控制器修改为异步。但是我遇到了以下异常:System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending. 我基本上正在尝试 `foreach(var request in requests) { dictionary.add(request.id, ProcessReqAsync(request)); }foreach(var entry in dictionary) { result.add(entry.Key, await entry.value); }return result;` - Ramesh
我认为那应该可以运行,你可能需要就此提出一个新问题。 - svick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接