停止线程,ManualResetEvent,volatile boolean或cancellationToken

我在Windows服务中有一个Thread(STAThread),它执行大量工作。 重新启动Windows服务时,我想优雅地停止此线程。

我知道几种方法

  • 一个不稳定的布尔值
  • ManualResetEvent的
  • 的CancellationToken

据我所知,Thread.Abort是不行的……

什么是最佳做法? 该工作是在另一个类中执行的,而不是启动线程的类,因此有必要在构造函数中引入cancellationToken参数,或者例如具有volatile变量。 但我无法弄清楚什么是最聪明的。

更新
为了澄清一点,我已经结束了一个我正在谈论的非常简单的例子。 如前所述,这是在Windows服务中完成的。 现在我正在考虑在循环中检查的volatile布尔值或者cancelToken ….我不能等待循环完成,如下所述它可能需要几分钟,使得服务器的系统管理员相信当服务需要重新启动时,服务有问题….我可以毫无问题地将循环中的所有工作都放在没有问题的情况下,但是我不能用Thread.Abort来做这件事它是“邪恶的”而且是COM调用接口,因此需要进行小的清理。

Class Scheduler{ private Thread apartmentThread; private Worker worker; void Scheduling(){ worker = new Worker(); apartmentThread = new Thread(Run); apartmentThread.SetApartmentState(ApartmentState.STA); apartmentThread.Start(); } private void Run() { while (!token.IsCancellationRequested) { Thread.Sleep(pollInterval * MillisecondsToSeconds); if (!token.IsCancellationRequested) { worker.DoWork(); } } } } Class Worker{ //This will take several minutes.... public void DoWork(){ for(int i = 0; i < 50000; i++){ //Do some work including communication with a COM interface //Communication with COM interface doesn't take long } } } 

UPDATE
刚刚检查了性能,使用了在代码中“检查”isCancelled状态的cancellationToken,比在ManualResetEventSlim上使用waitOne要快得多。 一些快速的图形,一个if取消,在for循环中迭代100.000.000次,花费我大约。 500毫秒,其中WaitOne的成本约为 3秒。 因此,在这种情况下,使用cancellationToken会更快。

您尚未发布足够的实施内容,但如果您可以使用,我强烈建议您使用CancellationToken 。 从可维护性的角度来看,使用和理解它非常简单。 如果您决定拥有多个工作线程,也可以设置合作取消。

如果您发现自己处于此线程可能长时间阻塞的情况,则最好设置您的体系结构,以便不会发生这种情况。 当你告诉他们停止时,你不应该开始那些不会很好玩的线程。 如果他们在你问他们时没有停止,那么唯一真正的方法就是拆掉这个过程并让操作系统杀死他们。

Eric Lippert在这里发布了一个与某个相关问题的精彩答案。

我倾向于使用bool标志,锁定对象和Terminate()方法,例如:

 object locker = new object(); bool do_term = false; Thread thread = new Thread(ThreadStart(ThreadProc)); thread.Start(); void ThreadProc() { while (true) { lock (locker) { if (do_term) break; } ... do work... } } void Terminate() { lock (locker) { do_term = true; } } 

除了Terminate()之外,所有其他字段和方法都是“worker”类的私有。

使用WaitHandle,最好是ManualResetEvent。 你最好的选择是让循环中的任何东西都完成。 这是实现目标最安全的方法。

 ManualResetEvent _stopSignal = new ManualResetEvent(false); // Your "stopper" ManualResetEvent _exitedSignal = new ManualResetEvent(false); void DoProcessing() { try { while (!_stopSignal.WaitOne(0)) { DoSomething(); } } finally { _exitedSignal.Set(); } } void DoSomething() { //Some work goes here } public void Terminate() { _stopSignal.Set(); _exitedSignal.WaitOne(); } 

然后使用它:

 Thread thread = new Thread(() => { thing.DoProcessing(); }); thread.Start(); //Some time later... thing.Terminate(); 

如果在“DoSomething”实现中有一个特别长时间运行的进程,则可能需要异步调用它,并为其提供状态信息。 但是,这可能会变得相当复杂 – 最好等到你的过程完成,然后退出,如果你能的话。

您可以在两种情况下找到您的主题:

  • 处理。
  • 阻塞。

在您的线程处理某些内容的情况下,您必须等待线程完成处理才能安全退出。 如果它是工作循环的一部分,那么您可以使用布尔标志来终止循环。

在您的线程阻塞的情况下,您需要唤醒您的线程并再次处理它。 线程可能在ManualResetEvent ,数据库调用,套接字调用或其他任何可能阻塞的阻塞上阻塞。 要将其唤醒,必须调用Thread.Interrupt()方法,该方法将引发ThreadInterruptedException

它可能看起来像这样:

 private object sync = new object(): private bool running = false; private void Run() { running = true; while(true) { try { lock(sync) { if(!running) { break; } } BlockingFunction(); } catch(ThreadInterruptedException) { break; } } } public void Stop() { lock(sync) { running = false; } } 

以下是如何使用它:

 MyRunner r = new MyRunner(); Thread t = new Thread(()=> { r.Run(); }); t.IsBackground = true; t.Start(); // To stop the thread r.Stop(); // Interrupt the thread if it's in a blocking state t.Interrupt(); // Wait for the thread to exit t.Join();