如何使用异步 Web 请求进行多线程

Posted

技术标签:

【中文标题】如何使用异步 Web 请求进行多线程【英文标题】:How to do multi-threading with asynchronous webrequests 【发布时间】:2012-04-28 12:09:16 【问题描述】:

我正在尝试实现 .NET 4 帮助程序/实用程序类,该类应基于 web 测试工具的 url 列表检索 html 页面源。该解决方案应具有可扩展性并具有高性能。

我已经研究并尝试了很多天不同的解决方案,但找不到合适的解决方案。

根据我的理解,实现我的目标的最佳方法是使用使用 TPL 并行运行的异步 Web 请求。

为了完全控制标头等。我使用 HttpWebResponse 而不是包装 HttpWebResponse 的 WebClient。在某些情况下,输出应该链接到其他任务,因此使用 TPL 任务可能是有意义的。

经过许多不同的试验/方法,我目前所取得的成就,

    实施了基本的同步、异步 (APM) 和并行(使用 TPL 任务)解决方案,以查看不同解决方案的性能水平。

    为了查看异步并行解决方案的性能,我使用了 APM 方法、BeginGetResponse 和 BeginRead,并在 Parallel.ForEach 中运行它。一切正常,我对性能感到满意。不知何故,我觉得使用简单的 Parallel.ForEach 不是要走的路,例如我不知道如何使用任务链。

    然后我尝试了更复杂的系统,使用任务来包装 APM 解决方案,方法是使用 TaskCompletionSource 和迭代器来迭代 APM 流。我相信这个解决方案可能是我正在寻找的,但是有一个奇怪的延迟,介于 6-10 秒之间,在运行 500 个 url 列表时会发生 2-3 次。

    根据日志,当延迟发生时,执行已返回到在循环中调用 async fetch 的线程。当执行移回循环时,延迟并不总是发生,只有 2-3 次,其他时候它工作正常。看起来循环线程将创建一组任务,这些任务将由其他​​线程处理,并且在大多数/所有任务完成时,在循环继续创建剩余任务和其他线程再次处于活动状态之前会有延迟(6-8 秒) .

循环内迭代器的原理是:

IEnumerable<Task> DoExample(string input) 
     
    var aResult = DoAAsync(input); 
    yield return aResult; 
    var bResult = DoBAsync(aResult.Result); 
    yield return bResult; 
    var cResult = DoCAsync(bResult.Result); 
    yield return cResult; 
    … 
    

Task t = Iterate(DoExample(“42”));

我正在使用 System.Net.ServicePointManager.DefaultConnectionLimit 解决连接限制并使用 ThreadPool.RegisterWaitForSingleObject 解决超时

我的问题很简单,实现帮助程序/实用程序类以检索 html 页面的最佳方法是:

具有可扩展性和高性能 使用网络请求 很容易与其他任务联系起来 能够使用超时 使用 .NET 4 框架

如果您认为我上面介绍的使用 APM、TaskCompletionSource 和迭代器的解决方案很好,我将不胜感激尝试解决延迟问题的任何帮助。

我对 C# 和 Windows 开发完全陌生,所以请不要介意我正在尝试的东西没有太大意义。

任何帮助都将受到高度赞赏,因为如果没有解决这个问题,我必须放弃我的测试工具开发。

谢谢

【问题讨论】:

您能否更详细地解释一下您是如何使用迭代器的,以及为什么您认为将其作为迭代器实际上很有用? 在尝试了各种解决方案后,我最终根据 msdn 博客上的 MS 专家建议使用了迭代器。我的解决方案或多或少与博客中的相同,只是添加了超时和日志记录。我没有任何特定的理由使用迭代器,我对任何可行的解决方案持开放态度。链接代码sn-p:social.msdn.microsoft.com/Forums/en-US/parallelextensions/… 【参考方案1】:

在 TPL 之前的 .NET 中使用迭代器是一个很好的解决方案(例如,MS Robotics 的协调和并发运行时 (CCR) 大量使用了迭代器并帮助启发了 TPL)。一个问题是迭代器本身并不能满足你的需求——你还需要一个调度器来有效地分配工作负载。这几乎是由您链接到的 Stephen Toub 的 sn-p 完成的 - 但请注意一行:

enumerator.Current.ContinueWith(recursiveBody, TaskContinuationOptions.ExecuteSynchronously);

我认为您看到的间歇性问题可能与强制“ExecuteSynchronously”有关 - 它可能导致可用内核/线程之间的工作分配不均。

看看斯蒂芬提出的其他一些替代方案in his blog article。特别是,看看仅仅做一个简单的 ContinueWith() 调用链接会做什么(如果需要,然后匹配 Unwrap() 调用)。语法不会是最漂亮的,但它是最简单的,并且尽可能少地干扰底层工作窃取运行时,因此您有望获得更好的结果。

【讨论】:

感谢您的建议和cmets。我会仔细看看斯蒂芬的博客。

以上是关于如何使用异步 Web 请求进行多线程的主要内容,如果未能解决你的问题,请参考以下文章

异步 Web 请求、EntityFramework 和 DI,它是如何工作的?

多线程实现简单的事件异步处理框架

27 | 新特性:Tomcat如何支持异步Servlet?

如何保护可能在多线程或异步环境中使用的资源?

使用多线程提高REST服务器性能

Web 多线程开发利器 Comlink 的剖析与思考