C# · 12月 26, 2021

c# – 并行.在大约137​​0次迭代后冻结,不知道为什么

我在一个超过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);

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

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

任何想法,我如何调试这个,看看什么是锁定,如果有什么?

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

@chibacity – 因为它是在后台任务,它不冻结UI.直到它冻结的时候,进度条和标签每秒约2秒.当它冻结,它只是停止移动.我可以验证它停止的号码已经被处理,但没有更多.双核2.2GHz的cpu使用率在运行期间为3-4%,一旦冻结,则为1-2%.

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

@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);}

你可以看到,这是非常简单的东西.一些转换方法有很多进展,但是它们都不应该被阻止.而如下所述,“等待”状态中有1024个线程都位于web服务调用上.我在这里读到http://www.albahari.com/threading/,32位机器上的.Net 4的MaxThreads默认为1023.

如何释放那些等待的线程呢?

解决方法 一个可能的解释:你已经把这个过程变成一个不能创建任何更多线程的状态,这是阻止工作进行的,这就是为什么一切都在停止.

坦白说,无论这个假设是否正确,你需要采取完全不同的方法来做到这一点. Parallel.For是错误的解决方法. (并行最适合于cpu绑定的工作,这里是IO绑定的工作.)如果您真的需要正在进行数千个Web服务请求,则需要转移到使用异步代码,而不是多线程代码.如果您使用异步API,您将能够同时启动数千个请求,同时仅使用少量线程.

无论这些请求是否能够同时执行,这是另一回事 – 无论您使用当前的“线程启示”实现还是使用更高效的异步实现,您都可能会遇到挫败. (.NET有时可以限制实际需求的数量).所以你可以要求尽可能多的请求,但是你可能会发现几乎所有的请求都是等待早期的请求来完成的.例如.我认为WebRequest限制与任何单个域的并发连接只有2 …启动1000个线程(或1000个异步请求)将导致加载更多的请求,等待是2个当前请求之一!

你应该做自己的节流.您需要确定同时有多少未完成的请求,并确保您只需一次启动该请求.只要求Parallel启动尽可能快的就可以把所有的东西全部掉下来.

更新添加:

快速修复可能是使用Parallel.For接受ParallelOptions对象的重载 – 您可以设置其MaxDegreeOfParallelism属性来限制并发请求数.这将阻止这个线程繁重的实现实际上用完线程.但是这个问题仍然是一个低效的解决方案. (所有我知道的是,你确实需要做出数千个并发请求,例如,如果你正在编写一个网页抓取工具,那实际上是一件合理的事情,并不是这个工作的正确的类,使用异步操作如果Web服务代理使用支持APM(BeginXxx,EndXxx),则可以将其包装到任务对象中 – Task.TaskFactory提供了一个FromAsync,它将提供一个表示正在进行的异步操作的任务.

但是,如果你要试图在一次飞行中有数千个请求,你需要仔细考虑你的扼制策略.只要尽可能快地将请求尽可能地放在最佳策略上.