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.
鉴于我在这里,我怎样才能释放那些等待的线程?
一个可能的解释是:你已经让流程进入了一个无法创建更多线程的状态,这阻碍了工作的进展,这就是为什么一切都停止了.
坦率地说,无论该假设是否正确,您都需要采取完全不同的方法.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
表示正在进行的异步操作的任务.
但是,如果您要尝试同时处理数千个请求,您需要仔细考虑您的限制策略.尽可能快地将请求丢弃在那里不太可能是最佳策略.
在VS调试器中运行该应用程序.当它似乎锁定时,告诉VS调试:全部中断.然后转到Debug:Windows:Threads并查看进程中的线程.其中一些应该显示并行for循环中的堆栈跟踪,这将告诉您在调试器停止进程时它们正在做什么.