Parallel.ForEach - 强制中断运行阻塞调用的日志

时间:2011-01-14 16:25:12

标签: .net multithreading foreach parallel-processing

对于无法完全脱线而感到高兴。实施例;

您有一个简单的Windows窗体应用程序,它连接到阻塞同步Web服务。在并行循环中,它在Web服务上执行一个函数。

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
    {

        ParallelProcessIntervalRequest(wcfProxyClient, item, loopState);

    });

Web服务调用需要2分钟来完成avg,(确实可以是任何阻塞的调用,例如Thread.Sleep,而不仅仅是Web服务)现在我将MaxDegreeOfParallelism设置为实用的20个线程。 iListOfItems中有1000个要处理的项目。

用户单击进程按钮并开始循环,非常好我们有20个线程全部运行在iListOfItems集合中的1000个项目上。大。

但是,由于某种原因用户需要关闭应用程序,他们会关闭表单。这20个线程将继续在所有1000个项目上超越,如果到目前为止只处理了40个,这将是不好的,现在这将是非常糟糕的,因为应用程序不会像用户期望的那样退出,但将继续在任务管理员看到的场景。

假设用户尝试在VS 2010中再次重建应用程序,它报告exe仍然被锁定,他们将不得不进入任务管理器并将其杀死。

你的喊叫,但当然,你应该使用新的并行取消构造取消所述线程...但是你知道,情况并没有好转,用户仍然要等到最后一次阻塞调用已经完成,在我们的例子中这是2分钟。此行为导致问题的方案还有很多。

所以我选择不调用CancellationTokenSource对象的Cancel函数,因为它引发了一个昂贵的异常,并且可以说违反了控制异常代码流的反模式。所以我实现了一个简单的线程安全属性 - StopExecuting。在循环中我检查StopExecuting的值,如果由外部影响设置为true,我在循环中进行以下调用;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || StopExecuting) {loopState.Stop(); return;}

因此,迭代可以以“受控”的方式退出,同时停止循环处理进一步的迭代,但正如我所说,这对我们的困境没什么作用。

在迭代中进行的长时间运行阻塞调用必须在我们检查是否应该停止之前完成。因此,当用户关闭表单时,可能会要求20个线程停止,但只有在完成执行长时间运行的函数调用时它们才会停止 - 这可能是平均2分钟。

在CancellationTokenSource上调用Cancel时也是如此。只有一旦迭代完成(不像线程中止那样中断),就会在所有其他线程最终完成并返回时缓存一个异常。在CancellationTokenSource上调用Cancel似乎不会在处理线程上抛出异常,这会像线程中止一样中断阻塞调用。

如果确实如此,那么在线程上调用线程中止也没有什么不同,因为这两种方式都会导致在线程中捕获的异常,以便在线程退出之前处理关闭和释放资源等

一个线程中止异常是你可以减轻“使系统处于不稳定/未定义状态”的主张,如果一个预期的情况,如关闭表单,随后说有时可能不可能的确实是对于程序员而言,确保它是一样的,顺便说一下他们编写循环以及他们选择维护的资源处理方式。考虑到所有事情,无法通过线程中止行为来中断阻塞调用,感觉就像一个工具已经从我们的技巧中丢失了。我必须在这样的实例中恢复正常的线程构造,以获得这个单一但重要的能力。羞。

这是一个问题,新并行库的短缺?如果没有,库如何使表单关闭时杀死这些线程,而不等待阻塞调用或让进程在后台运行。当然,如果我们使用'较旧'的线程原语和实际线程,这样的控制会相对简单。

3 个答案:

答案 0 :(得分:0)

不,这不是PTL的缺点。

房间里的大象是那两分钟的网络服务电话;他们不应该花那么长时间。

所有PTL都能做的就是呼叫代码。如果该代码阻塞,则无法发出信号停止,因此必须等待或中止线程。我认为它可以杀死线程和东西,但这很危险并且会引起更多的愤怒,因为人们会错误地使用它(请记住Thread.IsBackground?)。

想法1

个人选择是进行webservice调用,只是将消息放入队列(或使用ESB,如NServiceBus)并立即返回。然后,该消息将由单独服务的一个或多个实例处理(出于可伸缩性目的)。然后,只要它喜欢处理消息就可以使用该服务,并且您正在将并行性从客户端移动到服务器 - 无需在客户端上使用多个线程就意味着更简单的客户端。

然后您可以通过轮询或发回消息等来请求状态。在此期间,您可以选择将某种本地状态标记为“待定”或者向用户提供反馈。

创意2

如果网络服务电话无法控制并需要2分钟,那么您就遇到了麻烦。您可以编写自己的线程代码并在应用关闭时处理杀死线程,但这不是一个好主意。您可以将本地Windows服务作为始终运行的客户端应用程序的一部分,从本地计算机上的MSMQ队列处理消息。然后,您可以让该服务应用程序调用长时间运行的Web服务方法,并且仍然有一个快速关闭并响应的客户端应用程序?

创意3

有一个你控制的代理网络服务(即写自己)基本上完成我在上面的想法1中所说的,但是“处理消息”的行为实际上是在调用运行webservice。

答案 1 :(得分:0)

IMO,这不是并行库的问题。 对于每个线程构造,你基本上都有相同的原则问题:如果你想要一个巧妙的中止方法,你必须自己实现它。如果您使用“常规”线程,并使用信号系统进行纾困,则会遇到与并行库相同的问题:如果它刚刚进入阻塞呼叫持续两分钟,则需要两分钟才能发生挽救

由于您说您无法控制所调用的服务,因此我认为唯一的选择是了解如何重新设计自己的代码以考虑缓慢调用的可能性。

顺便说一句:我不是并行库的专家,但不是由作为后台线程实现的库产生的线程,即:它们不应该在应用程序关闭时自动拆除吗? / p>

答案 2 :(得分:0)

TPL最好的一点是它的可扩展性。如果你不喜欢它中的东西,很可能有一种方法可以替换你不喜欢的部分。如果您想要不同的排队语义,请实现自定义Task工厂;如果你想更严格地控​​制实际线程,他们的优先级,他们的公寓状态,......实现自定义TaskScheduler

在您的情况下,自定义TaskScheduler将允许您访问正在使用的所有线程,您可以将它们从您想要的中删除。不能说我会推荐,但它会起作用。

示例on my blog或MSDN。