2

我们使用 TPL 将长时间运行的任务排队到线程池中。有些任务可能会阻塞一段时间,所以我们使用以下模式来取消它们:

private void RunAction(Action action, CancellationTokenSourceWithException cts)
{

    try
    {
        s_logger.Info("Starting action on thread ID: {0}", Utils.GetCurrentNativeThreadId());

        Thread taskThread = Thread.CurrentThread;
        cts.Token.Register(() => InterruptTask(taskThread));

        s_logger.Info("Running next action");
        action();
    }
    catch (Exception e)
    {
        cts.Cancel(e);
        throw;
    }

这样,调用cts.Cancel()将导致任务线程被中断,以防它被阻塞。然而,这导致了一个问题:我们不知道线程是否真的得到了 ThreadInterruptedException。有可能我们调用Thread.Interrupt()它,但线程将运行到完成,任务将简单地结束。在这种情况下,线程池线程将有一个 ThreadInterruptedException 形式的定时炸弹,当另一个任务在该线程上运行并试图阻塞时,它就会得到这个异常。

一种Thread.ResetInterrupted()方法(类似于Thread.ResetAbort())在这里会有所帮助,但它似乎不存在。我们可以使用类似下面的东西:

try
{
    someEvent.Wait(10);
}
catch (ThreadInterruptedException) {}

吞下ThreadInterruptedException,但它看起来很难看。

任何人都可以提出替代方案吗?我们在线程池线程上调用 Thread.Interrupt 是错误的吗?这似乎是取消任务的最简单方法:使用事件等的协作取消使用起来要麻烦得多,并且必须从任务传播到我们使用的所有类中。

4

2 回答 2

5

您不能这样做,因为您不知道线程池的线程是否/何时会在不运行您自己的代码时阻塞!

除了您提到的问题之外,如果一个线程决定在不运行您自己的代码时阻塞,那么该线程将未被ThreadInterruptException处理并且应用程序将立即终止。这是您无法使用try/block/catch守卫解决的问题,因为存在竞争条件:守卫可能在Thread.Interrupt被调用时刚刚完成,因此如果运行时决定此时让线程阻塞,您将遇到崩溃。

因此,使用Thread.Interrupt不是一个可行的选择,您肯定必须设置合作取消。

除此之外,您可能首先不应该将线程池用于这些任务(尽管没有足够的数据可以使用。引用文档(强调我的):

如果您有需要后台处理的短任务,托管线程池是一种利用多个线程的简单方法。

有几种情况适合创建和管理自己的线程而不是使用线程池线程:

  • ...
  • 您的任务会导致线程长时间阻塞。线程池具有最大线程数,因此大量阻塞的线程池线程可能会阻止任务启动。
  • ...

因此,您可能要考虑使用自己的线程池(这里有一个显然非常有声望的实现

于 2012-08-16T11:48:58.323 回答
1

简单的。您需要将 CancellationToken 传递给被调用的操作,并在发出取消信号时对其进行操作。与 TPL 线程Interrupt混淆绝对是错误的行为,并且会使 TPL 处于“混乱”状态。一路采用取消模式。

于 2012-08-16T12:08:07.763 回答