停止Thread,ManualResetEvent,volatile boolean或cancellationToken

时间:2012-11-20 15:30:27

标签: c# multithreading cancellation cancellation-token

我在Windows服务中有一个线程(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
    }
  }
}

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

4 个答案:

答案 0 :(得分:4)

我倾向于使用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”类的私有。

答案 1 :(得分:3)

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

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

Eric Lippert对一个有点相关的问题here发布了一个很棒的答案。

答案 2 :(得分:3)

使用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”实现中有一个特别长时间运行的进程,您可能希望异步调用它,并为其提供状态信息。但是,这可能变得相当复杂 - 最好等到你的过程结束,然后退出,如果你能的话。

答案 3 :(得分:2)

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

  • 处理。
  • 阻止。

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

如果您的线程阻塞,那么您需要唤醒您的线程并再次处理它。线程可能在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();