使用Thread.Interrupt取消线程池工作项

tel*_*win 2 c# task-parallel-library

我们使用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;
    }
Run Code Online (Sandbox Code Playgroud)

这样,调用cts.Cancel()将导致任务线程被阻塞,以防它被阻塞.但是,这导致了一个问题:我们不知道线程是否实际上得到了ThreadInterruptedException.我们可能会调用Thread.Interrupt()它,但线程将运行完成,任务将结束.在这种情况下,线程池线程将以ThreadInterruptedException的形式出现一个滴答声炸弹,当另一个任务在此线程上运行并尝试阻塞时,它将获得此异常.

一个Thread.ResetInterrupted()方法(类似于Thread.ResetAbort())在这里会有所帮助,但它似乎并不存在.我们可以使用以下内容:

try
{
    someEvent.Wait(10);
}
catch (ThreadInterruptedException) {}
Run Code Online (Sandbox Code Playgroud)

吞下ThreadInterruptedException,但看起来很难看.

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

Jon*_*Jon 5

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

除了你提到的问题,如果一个线程在没有运行你自己的代码的情况下决定阻止,那么ThreadInterruptException将无法处理,应用程序将立即终止.这是你无法使用try/ block/catchguard解决的问题,因为存在竞争条件:防护可能刚刚在Thread.Interrupt调用时完成,因此如果运行时决定在该点处拥有线程阻塞,则会发生崩溃.

所以使用Thread.Interrupt不是一个可行的选择,你肯定必须建立合作取消.

除此之外,你可能不应该首先使用线程池来完成这些任务(虽然没有足够的数据.引用文档(强调我的):

如果您的短任务需要后台处理,则托管线程池是利用多个线程的简便方法.

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

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

因此,你可能要考虑使用自己的线程池(有一个明显非常有信誉的实施在这里).