杀死一个.NET线程

19

我创建了一个线程来执行某个方法。但有时即使线程仍在工作,我也想杀死这个线程。我该怎么办?我尝试了Thread.Abort(),但它会弹出一个消息框显示“线程已中止”。我该怎么办?


48
你是否有阅读 MSDN 中的“备注”部分?永远不要使用 ABORT,它应该作为最后的手段。如果你使用 THREAD.ABORT,会导致天塌下来,小猫会被杀死。 - J-16 SDiZ
1
小猫?我以为是小鸡 :) 但你的个人资料图片解释了你的评论! - Rashmi Pandit
4
正如其他人所说,尽你最大努力不要这样做。你应该尽量避免创建一个线程去执行你无法控制的任务。如果你必须杀死正在运行的代码但又无法进行控制,最安全的方法是在另一个进程中运行代码,而不是在另一个线程中运行。关闭进程比关闭线程更加安全可靠。 - Eric Lippert
8个回答

49

不要调用Thread.Abort()

Thread.Abort很危险。相反,您应该与线程合作,以便可以平静地关闭它。线程需要设计成可以被告知自杀,例如通过设置一个布尔变量keepGoing为false来停止线程。然后线程将会有类似于:

while (keepGoing)
{
    /* Do work. */
}

如果线程可能会在SleepWait中阻塞,那么可以通过调用Thread.Interrupt()来打断它们。线程应该准备好处理ThreadInterruptedException异常:

try
{
    while (keepGoing)
    {
        /* Do work. */
    }
}
catch (ThreadInterruptedException exception)
{
    /* Clean up. */
}

6
使用异常处理来控制程序流程是个糟糕的想法。相反,应该使用WaitHandle。 - Mark Seemann
6
我不认为那很可怕。如果你的循环不够紧凑,需要处理30秒钟才知道应该退出,那么抛出异常是一个完美的方式来停止它。 - Jimbo
6
如果你的线程正在阻塞等待输入,而你需要使其停止,该怎么办呢?由于它可能会无限期地阻塞,所以中断线程并处理异常是唯一让它退出的方式。 - Kiril
1
@Lirik 那么你应该使用非阻塞版本或可取消的异步调用。想想在Unix中的 select() - Jonathon Reinhart
那么,你如何从SqlCommand.ExecuteNonQuery()中获取线程?不要在这里调用Async(),这只是把问题移动了。 - Joshua

26

你应该只在万不得已时才调用Abort()。相反,你可以使用一个变量来同步线程:

volatile bool shutdown = false;

void RunThread()
{
   while (!shutdown)
   {
      ...
   }
}

void StopThread()
{
   shutdown = true;
}

这允许您的线程干净地完成其工作,使您的应用程序处于已知良好状态。


9
最正确和线程安全的方法是使用WaitHandle来通知线程何时停止。我通常使用ManualResetEvent。
在您的线程中,您可以有:
private void RunThread()
{
    while(!this.flag.WaitOne(TimeSpan.FromMilliseconds(100)))
    {
        // ...
    }
}

这里的this.flag是ManualResetEvent的实例。这意味着你可以在线程外部调用this.flag.Set()来停止循环。

当标志被设置时,WaitOne方法才会返回true。否则,它将在指定的超时时间(例如100毫秒)后超时,线程将再次运行循环。


1
你的实现方式的缺点是,在每个循环中,你都要等待事件的发生,什么也不做。我宁愿相反地采取另一种方式。设置一个指示关闭的标志,并发出线程已经结束执行的信号。while (!WillTerminate) {..} HasTerminate.Set(); - mathk
这个能在“BackgroundWorker”的“DoWork”事件中工作吗? - Ehsan Sajjad

7
不建议强制终止线程,最好的方法是发出信号让它优雅地结束。有多种不同的方式来实现这一点。
  • 如果线程被阻塞,可以使用 Thread.Interrupt 来唤醒它。
  • 轮询标志变量。
  • 使用 WaitHandle 类发送信号。
由于我已经在这个答案中详细介绍了每种方法的用法,因此无需重复解释。

2
+1. 你能提供一个简单的例子,通过等待句柄来终止/停止一个线程吗? - Royi Namir

2

我同意Jon B的观点

volatile bool shutdown = false;

void RunThread()
{

try
{
    while (!shutdown)
    {
        /* Do work. */
    }
}
catch (ThreadAbortException exception)
{
    /* Clean up. */
}
}

void StopThread()
{
   shutdown = true;
}

2

放弃一个线程是一个非常糟糕的想法,因为你无法确定线程在中止时正在执行什么任务。

相反,可以设置一个属性,让线程可以检查,同时你的外部代码也可以设置该属性。当线程处于安全退出位置时,让线程检查这个布尔属性。


1

0

编辑:

  1. 我为此创建了一个小类

  2. 注意到它不能与async/await一起使用

  3. 发布了一个更新的类,然后Theodor Zoulias(谢谢:))让我知道这个想法是有缺陷的。

  4. 现在我重新发布原始类(不能与async/await一起使用!)

    var t = new StopAbleThread(isShutDown =>
     {
       Console.WriteLine("a");
       while (!isShutDown())
       {
         Console.WriteLine("b");
         Thread.Sleep(TimeSpan.FromMinutes(10));
       }
     }  )   
    
    { IsBackground = true };
       t.Start( );
    

像这样停止线程:

t.Stop();

这个类:

public class StopAbleThread
{
  public bool IsBackground
  {
    set => _thread.IsBackground = value;
  }

  private readonly Thread _thread;
  private volatile bool _shutdown;

  public delegate bool IsShutDown( );
  public delegate void CodeToRun( IsShutDown isShutDown );


  public StopAbleThread( CodeToRun codeToRun )
  {
    _thread = new Thread( ( ) =>
    {
      try
      {
        codeToRun( _IsShutDown );
      }
      catch( ThreadInterruptedException )
      {
        //ignore
      }
    } );
  }

  private bool _IsShutDown( )
  {
    return _shutdown;
  }

  public void Start( )
  {
    _thread.Start( );
  }

  public void Stop( )
  {
    _shutdown = true;
    _thread.Interrupt( );
  }
}

Thread 构造函数不理解异步委托。您可以在这里阅读有关此问题的内容(https://dev59.com/91cP5IYBdhLWcg3wd5um)或者在这里(https://dev59.com/oYrda4cB1Zd3GeqPM3vO)。因此,`_thread.Interrupt()` 将中断一个已经终止的线程,所以在实践中它将不起作用。 - Theodor Zoulias
感谢您的评论。上面的代码对于我的使用情况来说是按预期工作的。中断调用确实会在睡眠时唤醒线程。 - Rowi123
你的答案的第一个版本是OK的,因为所有代码都是同步的。但是通过添加async / await,你创建了一个有缺陷的版本,这个版本不会始终正常工作。不能保证在await点之后的代码将在与之前相同的线程上继续运行。在你的示例中,await Task.Delay(1)_thread将要终止的地方,而随后的Thread.Sleep(FromMinutes(10))将使一个ThreadPool线程进入睡眠状态,而不是_thread。然后,_thread.Interrupt();将无法起任何作用。 - Theodor Zoulias
如果你不相信我,可以在await之前和之后记录Thread.CurrentThread.ManagedThreadId并查看它是否相同,以此来验证。 - Theodor Zoulias
好的,再次感谢。我是否应该重新发布第一个解决方案?而且我真的需要一个带有await的解决方案。你知道更好的方法吗? - Rowi123
是的,我建议回滚到版本1。至于如何非合作地终止异步操作,我真的不知道! - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接