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

Lao*_*wai 5 c# multithreading asynchronous httpwebrequest task-parallel-library

我正在尝试实现.NET 4 helper/utility类,它应该根据webtesting工具的url列表检索HTML页面源.解决方案应该是可扩展的并且具有高性能.

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

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

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

在经过许多不同的试验/方法之后,我迄今取得的成就,

  1. 实施基本同步,异步(APM)和并行(使用TPL任务)解决方案,以查看不同解决方案的性能级别.

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

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

    根据日志,执行已返回到发生延迟时在循环中调用异步提取的线程.当执行移回循环时,延迟不会发生,只需2-3次,其他时候工作正常.看起来循环线程会创建一组由其他线程处理的任务,而大多数/所有任务都完成后,在循环继续创建剩余任务之前会有延迟(6-8s),其他线程再次处于活动状态.

迭代器内部循环的原理是:

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”));
Run Code Online (Sandbox Code Playgroud)

我正在使用System.Net.Service.Manager.DefaultConnectionLimit解析连接限制并使用ThreadPool.RegisterWaitForSingleObject超时

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

  • 具有可扩展性和高性能
  • 使用webrequests
  • 很容易被链接到其他任务
  • 能够使用超时
  • 使用.NET 4框架

如果您认为使用上面提到的APM,TaskCompletionSource和iterator的解决方案很好,我将不胜感激任何尝试解决延迟问题的帮助.

我对C#和Windows开发都很陌生,所以请不要介意我正在尝试的东西是不是太有意义.

任何帮助都将受到高度赞赏,因为没有得到解决我必须放弃我的测试工具开发.

谢谢

Lar*_*ann 0

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

enumerator.Current.ContinueWith(recursiveBody, TaskContinuationOptions.ExecuteSynchronously);
Run Code Online (Sandbox Code Playgroud)

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

看一下 Stephen在他的博客文章中提出的其他一些替代方案。特别是,看看仅执行简单的ContinueWith() 调用链接会做什么(如有必要,然后进行匹配的Unwrap() 调用)。语法不会是最漂亮的,但它是最简单的,并且对底层工作窃取运行时的干扰尽可能小,因此您有望获得更好的结果。