【问题标题】:Cancelling a threadpool workitem with Thread.Interrupt使用 Thread.Interrupt 取消线程池工作项
【发布时间】:2012-08-12 18:01:01
【问题描述】:

我们正在使用 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 是错误的吗?这似乎是取消任务的最简单方法:使用事件等的协作取消使用起来要麻烦得多,并且必须从任务传播到我们使用的所有类中。

【问题讨论】:

    标签: c# task-parallel-library


    【解决方案1】:

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

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

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

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

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

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

    • ...
    • 您的任务会导致线程长时间阻塞。线程池有最大线程数,所以大 阻塞的线程池线程数可能会阻止任务 开始。
    • ...

    因此,您可能需要考虑使用自己的线程池(显然有一个非常著名的实现 here)。

    【讨论】:

    • 我使用的是 TPL,而不是直接使用线程池。我的印象是TaskCreationOptions.LongRunning 标志是专门为长时间运行的任务设计的。另外,即使我推出自己的线程池,我也会遇到同样的问题。
    • @telewin:见stackoverflow.com/questions/3105988/…。似乎LongRunning 将创建单独的线程,但是 AFAIK 这没有在任何地方记录,因此可能不希望依赖它。当然,取消问题是与池问题不同的问题。
    • 确实 - 这些是不同的问题。我对取消问题更感兴趣。我正在使用 TPL 来运行长时间运行的任务,从这个意义上说,我不在乎它如何安排它们。我想做的是在我的操作线程中“处理” Thread.Interrupt 请求,无论它是否阻塞。
    【解决方案2】:

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

    【讨论】:

    • 我同意这种方法可行,但它会使代码变得非常复杂。例如,我不能在我的代码中使用任何lock() {} 语句,我必须将它们全部转换为Monitor(或WaitHandle,或其他)。我希望 Thread.Interrupt 能让我编写更简洁的代码,我认为按照我在原始问题中的建议吞下 ThreadInterruptedException 可以解决您描述的问题,不是吗?
    猜你喜欢
    • 2011-05-12
    • 2013-07-09
    • 2018-01-20
    • 2021-08-08
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 2016-12-23
    • 2014-06-07
    相关资源
    最近更新 更多