5

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

我知道几种方法

  • 一个不稳定的布尔值
  • 手动复位事件
  • 取消令牌

据我发现 Thread.Abort 是不行的......

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

更新
只是为了澄清一点,我已经总结了一个非常简单的例子来说明我在说什么。如前所述,这是在 Windows 服务中完成的。现在我在想一个在循环中检查的 volatile boolean 或 cancelToken.... 我不能等待循环完成,如下所述,它可能需要几分钟,使服务器的系统管理员相信当他们需要重新启动服务时,服务出现问题......接口被调用,因此需要进行小清理。

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 快得多。一些快速计算者,如果在 cancelToken 上在 for 循环中迭代 100.000.000 次,我大约花费了我。500 毫秒,其中 WaitOne 的成本约为。3 秒。因此在这种情况下的性能使用cancellationToken 会更快。

4

4 回答 4

4

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

于 2012-11-20T16:16:06.397 回答
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() 之外,所有其他字段和方法都是“工人”类私有的。

于 2012-11-20T15:40:42.783 回答
4

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

如果您发现自己处于此线程可能会长时间阻塞的情况,最好设置您的体系结构,以免发生这种情况。当您告诉它们停止时,您不应该启动不会很好玩的线程。如果你问他们时他们没有停止,唯一真正的方法是拆除进程并让操作系统杀死他们。

Eric Lippert 在这里对一个有点相关的问题发表了精彩的回答。

于 2012-11-20T16:08:06.773 回答
3

有两种情况下您可能会找到您的线程:

  • 加工。
  • 阻塞。

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

在您的线程阻塞的情况下,您需要唤醒您的线程并再次处理它。一个线程可能会阻塞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();
于 2012-11-20T16:03:36.783 回答