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

.net Parallel.ForEach-强制中断日志运行阻塞调用,.net,multithreading,foreach,parallel-processing,.net,Multithreading,Foreach,Parallel Processing,对于无法中止线程一点都不满意。榜样 您有一个简单的windows窗体应用程序,它连接到阻塞同步web服务。在其中,它在并行循环中对web服务执行函数 Parallel.ForEach(iListOfItems, po, (item, loopState) => { ParallelProcessIntervalRequest(wcfProxyClient, item, loopState); }); 在avg上完成web服务调用需要2分钟,实际上可能是

对于无法中止线程一点都不满意。榜样

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

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

        ParallelProcessIntervalRequest(wcfProxyClient, item, loopState);

    });
在avg上完成web服务调用需要2分钟,实际上可能是任何阻塞的调用,例如Thread.Sleep,而不仅仅是web服务现在我将MaxDegreeOfParallelism设置为20个线程。IListoItems中有1000个要处理的项目

用户单击process按钮,循环开始,非常好,我们有20个线程,所有线程都在items集合中的1000个项目上运行。太好了

然而,由于某种原因,用户需要关闭应用程序,他们会关闭表单。这20个线程将继续在所有1000个项目上运行,如果到目前为止只处理了40个项目,这将是不好的,现在这将是非常糟糕的,因为应用程序不会像用户期望的那样退出,而是将继续在后台运行,如task Manager中所示

假设用户在VS2010中再次尝试重建应用程序,它报告exe仍处于锁定状态,他们必须进入任务管理器并将其杀死

你的叫喊,但当然,你应该使用新的并行取消结构取消所说的线程。。。但是你知道,情况并没有变得更好,用户仍然需要等到最后一个阻塞调用完成,在我们的示例中是2分钟。这种行为会导致更多的场景出现问题

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

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || StopExecuting) {loopState.Stop(); return;}
因此迭代可以以“受控”的方式退出,同时停止循环处理进一步的迭代,但正如我所说的,这对我们的困境几乎没有帮助

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

对CancellationTokenSource调用Cancel也是如此。只有在迭代完成后,不会像线程中止那样中断,才会缓存一个异常,以备所有其他线程最终完成并返回时使用。在CancellationTokenSource上调用Cancel似乎不会在处理线程上引发异常,这会像线程中止一样中断阻塞调用

如果它这样做了,那么不管怎样,调用线程上的线程中止都没有什么不同,因为这两种方法都会导致异常,在线程退出之前,异常可能会在线程中被捕获,以处理关闭和释放资源等问题

线程中止异常是指,如果出现诸如关闭表单之类的预期情况,您可以减轻让系统处于不稳定/未定义状态的声明,也就是说,有时可能不可能,这确实是程序员需要确保的事情,通过他们编写循环代码的方式,以及他们选择维护的资源处理方式。综上所述,无法用类似线程中止的行为来中断阻塞调用,这让人感觉像是从我们的技巧袋中丢失了一个工具。在这种情况下,我必须恢复到正常的线程构造,以获得这一单一但重要的功能。羞耻


那么这是一个问题吗,新的并行库中出现了一个短消息?如果没有,库如何使我们能够在关闭窗体时杀死这些线程,而不必等待阻塞调用或让进程在幕后运行。当然,如果我们使用“旧的”线程原语和实际线程,这种控制将相对简单

不,这不是PTL的缺点


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


PTL所能做的就是调用代码。如果代码阻塞,它就无法发出停止的信号,因此必须等待或中止线程。我想它可能会杀死线程和其他东西,但这是危险的,而且会引起比不能杀死更多的愤怒,因为人们会错误地使用它来记住线程。IsBackground

想法1 个人选择是进行webservice调用,将消息放在队列中,或者使用类似ESB的NServiceBus并立即返回。然后,该消息将由scalabi的一个或多个单独服务实例处理 目的。然后,该服务可能需要处理消息的时间,并且您正在将并行性从客户机移动到服务器-不需要在客户机上使用多个线程意味着更简单的客户机

然后,您可以稍后通过轮询或发回消息等方式请求状态。同时,您可以选择将某种本地状态标记为挂起或向用户提供反馈

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

想法3
拥有一个你可以控制的代理Web服务,也就是说,你自己编写的代理Web服务基本上完成了我在上面的想法1中所说的,但是处理消息的行为实际上是调用长期运行的Web服务。

IMO,这不是并行库的问题。 对于每个线程构造,您基本上都有相同的原则问题:如果您想要一种简洁的中止方法,您必须自己实现它。如果您使用常规线程,并使用信号系统进行纾困,那么它将遇到与并行库相同的问题:如果它刚刚输入了一个持续两分钟的阻塞调用,则需要两分钟才能进行纾困

既然你说你不能控制你调用的服务,我认为唯一的选择就是看看你如何重新设计你自己的代码,以考虑到缓慢调用的可能性


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

TPL的一个优点是它的可扩展性。如果你不喜欢里面的东西,有可能会有办法替换你不喜欢的部分。如果需要不同的队列语义,请实现自定义任务工厂;如果您想更严格地控制实际线程、它们的优先级、它们的单元状态。。。实现自定义TaskScheduler

在您的情况下,自定义TaskScheduler将允许您访问正在使用的所有线程,并且您可以根据自己的意愿将它们杀死。我不能说我会推荐,但它会起作用的


示例或MSDN上。

您是否真的有问题,或者您只是在观察一个显而易见的事实,即编写正确的线程代码是困难的?房间里的大象就是那两分钟的Web服务调用。你不应该这样做。如果是我,我会进行webservice调用,将消息放在队列中,或者使用类似ESB的NServiceBus,在客户端几乎立即返回的情况下,用多长时间处理该消息。然后,您可以稍后通过轮询或发回消息等方式请求状态。完全同意。。。在理想的世界里。这也许有点回避了这个问题的答案。不幸的是,web服务不在我们的控制之下。对于第三方库中可能存在的函数,或者存在可能导致长时间操作的资源争用的函数,可以进行许多调用。我找不到任何地方可以从外部中断并行循环的执行,除了正在摧毁应用程序域的重锤。PTL所能做的就是调用代码。我想它可能会杀死线程和其他东西,但这是危险的,而且会引起比不能记住线程更大的愤怒,因为人们会错误地使用它来记住线程。IsBackground?。如果webservice调用需要2分钟,那么我担心,只要你阻止每个线程使用内置的取消功能或你自己的功能迭代到下一个项目上,你就必须等待每个线程返回,然后你的应用程序才能关闭-你的手被束缚住了。我认为并行库在很多方面都很好,别误会我。但我仍然相信,在得到如此危险的工具之前,有人需要知道他们在做什么。我不认为它抽象了危险,可以被误用,就像线程中止的不当使用一样。让我们在我的示例中加入一些误用的范围。。。假设在循环中,我们引发一些由UI线程/表单订阅的事件。如果用户关闭表单,事件仍可能引发,并导致一些关于不存在的UI处理对象等的问题,当然,这些问题也可能会让用户少经历一些麻烦。我还是不明白为什么打断别人是件坏事idea@Jason:关于某人,需要知道他们在做什么:这是并行库的最大问题,以及.NET 5中的异步功能将带来的其他线程改进:L
很多人在没有正确的线程知识的情况下开始使用它们。一个人在处理这些事情时肯定需要知道自己在做什么。没有简单的方法,只有更简单易读的方法;这是真的,但在那个例子中,我的取消选项包括thread.abort也是真的。在使用并行函数时,据我所知,没有办法中断。它的线程完全是黑盒线程池线程,从我的代码中无法访问。是的,我同意有一种可能性已经从您的控制中移除。我不使用线程。中止,永远。也许这就是为什么我更倾向于重新设计代码。但我理解您所面临的问题。至少,关闭表单的行为不会关闭ForEach线程的执行。该过程将一直保持,直到所有线程完成各自的迭代。windows窗体可能会被释放,而且早已消失,但线程和主机进程/应用程序域似乎在用户不知道的情况下仍然存在。我喜欢它。。。但我在这方面有一定程度的认知失调,我也觉得对于这样一个简单的要求,我真的必须开始编写自己的任务调度器。但我认为这是迄今为止最接近解决问题的答案。因此,您可以通过ParallelOptions.TaskScheduler注入TaskScheduler实现。TaskScheduler是否与正在运行的循环线程同时处理取消请求。我想给CancellationTokenSource.Cancel函数添加一个重载,以使bool中止。我觉得需要更多的阅读P