Parallel.For在大约137​​0次迭代后冻结,不知道为什么

Chr*_*way 10 c# parallel-processing freeze task-parallel-library

我在7500多个对象上运行一个Parallel.For循环.在for循环中,我正在为每个对象做很多事情,特别是调用两个Web服务和两个内部方法.Web服务只是检查对象,处理并返回一个字符串,然后我将其设置为对象上的属性.两种内部方法也是如此.

我没有写任何东西到磁盘或从磁盘读取.

我还在带有标签和进度条的winforms应用程序中更新UI,以便让用户知道它在哪里.这是代码:

var task = Task.Factory.StartNew(() =>
{
  Parallel.For(0, upperLimit, (i, loopState) =>
  {
     if (cancellationToken.IsCancellationRequested)
        loopState.Stop();
     lblProgressBar.Invoke(
       (Action)
       (() => lblProgressBar.Text = string.Format("Processing record {0} of {1}.", (progressCounter++), upperLimit)));
     progByStep.Invoke(
       (Action)
       (() => progByStep.Value = (progressCounter - 1)));

      CallSvc1(entity[i]);
      Conversion1(entity[i]);
      CallSvc2(entity[i]);
      Conversion2(entity[i]);
  });
}, cancellationToken);
Run Code Online (Sandbox Code Playgroud)

这是在Win7 32位机器上进行的.

关于为什么当增量器大约在1370左右时突然冻结的任何想法(这是1361,1365和1371)?

关于如何调试这个并看看有什么锁定的任何想法?

编辑:
以下评论的一些答案:
@BrokenGlass - 不,没有互操作.我将尝试x86编译并让你知道.

@chibacity - 因为它是在后台任务上,所以它不会冻结UI.直到它冻结的时间,进度条和标签每秒大约2点.当它冻结时,它就会停止移动.我可以验证它停止的号码是否已被处理,但不再处理.双核2.2GHz的CPU使用率在运行期间最低,每次3-4%,冻结后1-2%.

@Henk Holterman - 到达1360需要大约10-12分钟,是的,我可以验证所有这些记录是否已经处理但不是剩余的记录.

@CodeInChaos - 谢谢,我会试试!如果我拿出并行代码,代码确实有用,它只需要一天又一天.我没有尝试过限制线程数,但是会.

编辑2:
关于Web服务发生了什么的一些细节

基本上,Web服务正在发生的是它们传递一些数据并接收数据(XmlNode).然后在Conversion1进程中使用该节点,该进程又在实体上设置另一个属性,该属性被发送到CallSvc2方法,依此类推.它看起来像这样:

private void CallSvc1(Entity entity)
{
    var svc = new MyWebService();
    var node = svc.CallMethod(entity.SomeProperty);
    entity.FieldToUpdate1.LoadXml(node.InnerXml);
}
private void Conversion1(Entity entity)
{
    // Do some xml inspection/conversion stuff
    if (entity.FieldToUpdate1.SelectSingleNode("SomeNode") == "something") {
        entity.FieldToUpdate2 = SomethingThatWasConverted;
    }
    else {
        // Do some more logic
    }
}
private void CallSvc2(Entity entity)
{
    var svc = new SomeOtherWebService();
    var xmlNode = svc.MethodToCall(entity.FieldToUpdate2.InnerXml);
    entity.AnotherXmlDocument.LoadXml(xmlNode.InnerXml);
}
Run Code Online (Sandbox Code Playgroud)

如你所见,这是非常简单的东西.在某些转换方法中有很多内容,但它们都不应该阻塞.如下所述,处于"等待"状态的1024个线程都位于Web服务调用上.我在这里阅读http://www.albahari.com/threading/,在32位机器上,MaxThreads默认为.Net 4的1023.

鉴于我在这里,我怎样才能释放那些等待的线程?

Ian*_*ths 9

一个可能的解释是:你已经让流程进入了一个无法创建更多线程的状态,这阻碍了工作的进展,这就是为什么一切都停止了.

坦率地说,无论该假设是否正确,您都需要采取完全不同的方法.Parallel.For是解决这个问题的错误方法.(Parallel最适合CPU绑定的工作.这里有你的IO工作.)如果你真的需要有数千个Web服务请求正在进行中,你需要转而使用异步代码而不是多线程代码.如果使用异步API,则只需使用少量线程即可同时启动数千个请求.

这些请求是否实际上能够同时执行是另一回事 - 无论您使用当前的"线程启动"实现还是更高效的异步实现,您可能会遇到限制.(.NET有时可以限制它实际发出的请求数量.)因此,您可以要求尽可能多地发出请求,但您可能会发现几乎所有请求都在等待早期请求完成.例如,我认为WebRequest将任何单个域的并发连接限制为仅2 ...将1000多个线程(或1000多个异步请求)连接起来只会导致更多请求等待成为当前2个请求之一!

你应该做自己的节流.您需要确定同时有多少未完成的请求,并确保您一次只启动那么多请求.只要求Parallel尽可能快地发射尽可能多的东西就会使一切都陷入困境.

更新以添加:

快速修复可能是使用Parallel.For接受ParallelOptions对象的重载- 您可以设置其MaxDegreeOfParallelism属性以限制并发请求的数量.这将阻止这个线程繁重的实现实际耗尽线程.但它仍然是解决问题的低效办法.(而且就我所知,你实际上需要制作成千上万的并发请求.例如,如果你正在编写一个网络爬虫,那实际上是一件合理的事情.Parallel但是,这个工作并不适合.使用异步操作.如果您正在使用的Web服务代理支持APM(BeginXxx,EndXxx),您可以将其包装在Task对象中 - Task.TaskFactory提供一个FromAsync表示正在进行的异步操作的任务.

但是,如果您要尝试同时处理数千个请求,您需要仔细考虑您的限制策略.尽可能快地将请求丢弃在那里不太可能是最佳策略.


dth*_*rpe 5

在VS调试器中运行该应用程序.当它似乎锁定时,告诉VS调试:全部中断.然后转到Debug:Windows:Threads并查看进程中的线程.其中一些应该显示并行for循环中的堆栈跟踪,这将告诉您在调试器停止进程时它们正在做什么.