.net 强制终止Parallel.ForEach生成的线程

wwodge7n  于 2023-03-24  发布在  .NET
关注(0)|答案(1)|浏览(450)

在使用Parallel.ForEach()时,有没有办法在特定线程上强制执行Thread.Abort
我知道Thread.Abort()是不推荐的。
我正在一个包含成千上万个实体的集合上运行Parallel.ForEach()
在某些情况下,循环处理的数据可以追溯到30年前。我们遇到过一些线程挂起的问题。当我们试图掌握这一点时,我们希望调用implement a fail safe。如果线程运行的时间超过x,强制杀死线程。
我不想使用取消令牌。
这将是丑陋的,但还没有找到另一种解决方案。是否有可能:

  • 让每个线程打开一个定时器。将Thread.CurrentThread的引用传递给定时器
  • 如果计时器超时,而处理尚未完成,则在该计时器上调用Thread.Abort
  • 如果需要,发出事件等待句柄信号,以允许下一位患者进行处理
private void ProcessEntity(ProcessParams param,
    ConcurrentDictionary<long, string> entities)
{
    var options = new ParallelOptions
    {
        MaxDegreeOfParallelism = 2
    };
    Parallel.ForEach(person, options, p =>
    {
        ProcessPerson(param, p);
    });
}

internal void ProcessPerson(ProcessParams param, KeyValuePair<long, string> p)
{
    try
    {
        //...
    }
    catch (Exception ex)
    {

    }
    param.eventWaitHandle?.WaitOne();
}
0yg35tkg

0yg35tkg1#

看起来Parallel.ForEach方法在其工作线程为aborted时没有弹性,并且行为不一致。其他时候它传播包含ThreadAbortExceptionAggregateException,其他时候它直接抛出ThreadAbortException,并显示丑陋的堆栈跟踪its internals
同样的情况也发生在ThreadInterruptException s上。这些异常是Thread.Interrupt方法的结果。引用最近的GitHub问题:
请注意,绝大多数代码,包括核心库中的代码,并没有针对线程中断的使用进行强化。这些可能会导致异常出现在线程处于等待/睡眠/加入状态的几乎任何地方,这意味着几乎所有的阻塞操作,获取锁的时间等,并且大多数代码都没有编写以适应此类异步异常。
解决这个问题的一种方法是限制Thread.Abort只在特定代码区域的影响,不允许它逃逸到您无法控制的代码路径。从.NET 7开始,有一个新的API ControlledExecution.Run可以用于此目的。此方法需要一个Action和一个CancellationToken。如果在操作运行时取消令牌,当前线程被中止。然后ThreadAbortException被捕获并处理,中止是reset,以便线程可以存活,并传播可以正常处理的OperationCanceledException。下面是它的使用方法:

Parallel.ForEach(persons, options, p =>
{
    using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
    {
        ControlledExecution.Run(() =>
        {
            ProcessPerson(param, p);
        }, cts.Token);
    }
});

这是一个危险的API,Microsoft不建议在生产代码中使用它。它主要用于测试相关的代码。
显然.NET Framework不包含ControlledExecution.Run API,但手动实现它并不是很困难。您可以在这个答案中找到一个实现(RunAbortable方法)。
.NET Core和.NET 6不支持中止线程。在这些平台上,您可以尝试interrupting线程而不是中止它们。它并不总是有效和响应,也不是100%安全(正如前面提到的最近的GitHub问题所证明的那样)。您可以在同一个答案中找到RunInterruptible实现。它可以在所有.NET平台上运行,旧的和新的。

相关问题