【问题标题】:How to stop worker threads in a multithreaded Windows service on service stop如何在服务停止时停止多线程 Windows 服务中的工作线程
【发布时间】:2009-04-01 10:51:00
【问题描述】:

我有一个 Windows 服务,它使用生产者/消费者队列模型和多个工作线程处理队列外的任务。这些任务可能会运行很长时间,即使不是几小时也可能是几分钟,并且不涉及循环

我的问题是关于处理服务停止以优雅地结束这些工作线程上的处理的最佳方式。我在另一个SO question 中读到,使用 thread.Abort() 是糟糕设计的标志,但似乎服务 OnStop() 方法在服务终止之前只给了有限的时间来完成。我可以在 ThreadAbortException 的捕获中进行足够的清理(不存在状态不一致的危险),因此在工作线程上调用 thread.Abort() 对我来说似乎没问题。是吗?有哪些替代方案?

【问题讨论】:

  • 有源代码示例的最终解决方案吗?

标签: .net multithreading windows-services


【解决方案1】:

确实,应该避免使用Abort。最好给它们一些时间优雅地退出 - 然后也许在超时后,也许考虑中止它们 - 但最终,服务停止也可以通过终止进程来实现同样的效果。 p>

我会尝试在我的队列中添加一个“刷新并退出”的信号 - 很像 Close 方法 here,但在完成时会有某种信号。

如果你求助于Abort - 认为这个过程受到了致命的伤害。尽快杀死它。

【讨论】:

  • 工作进程运行时间很长(我已经在问题中澄清了这一点),所以等待通常不是一种选择。
【解决方案2】:

为“关闭”创建一个任务类型,并为每个工作线程将其注入您的生产者/消费者队列一次。

然后使用 Thread.Join(带有超时)确保关闭已完成。

【讨论】:

  • 工作进程运行时间很长,所以在大多数情况下将消息放入队列将无法正常工作(无论如何我都这样做以防万一)
  • 所以标志方法可能更好,然后作业需要定期检查此标志(并且可能保存剩余的作业)。
【解决方案3】:

使用 .NET 4.0,您可以利用 System.Threading.Tasks 命名空间来利用 Task 对象。简而言之,您可以分配一个CancellationToken 来更优雅地处理任务中的取消/中止,无论是长时间运行还是短期运行。

请参阅 here 了解 MSDN 的更多详细信息。

【讨论】:

    【解决方案4】:

    修正后的问题实际上与线程无关,更多地与如何停止长时间运行的操作有关。就我个人而言,我总是将 APM 用于冗长的流和通信活动,例如大文件传输。每个回调在 IO 完成池线程上运行并快速完成,处理适度的块并安排下一次传递。挂起的操作可以简单地通过在套接字对象上调用Close() 来取消。这比 DIY 线程管理更便宜、更高效。


    如前所述,Abort() 是恶业,应该避免。

    以下材料是在从问题中排除循环案例之前编写的。

    当长时间运行的进程循环时,它们应该所有在它们的循环条件中包含一个退出标志,以便您可以向它们发出退出信号。

    bool _run; //member of service class
    
    //in OnStart
    _run = true;
    
    //in a method on some other thread
    while ((yourLoopCondition) & _run) 
    { 
      //do stuff
      foreach (thing in things) 
      {
        //do more stuff
        if (!_run) break;
      }
    }
    if (!_run) CleanUp();
    
    //in OnStop
    _run = false;
    

    严格来说,您应该使用 volatiles,但由于只有控制逻辑会设置标志,所以这无关紧要。从技术上讲,存在竞争条件,但这只是意味着您可能会再循环一次。

    【讨论】:

    • 没有循环的情况下可以长时间运行线程。我在网络连接和外部进程方面遇到了这个问题。 (而且我仍在寻找停止线程的好方法。)
    • 是的,完全同意我的长期运行过程不涉及任何循环,所以这个解决方案不是一个选项。
    • 你到底在做什么,需要很长时间没有循环,复制一个巨大的文件?
    • 如果您无法发出停止信号,那么中止是您唯一的选择。准备好进行一些丑陋的清理工作。
    【解决方案5】:

    使用 ManualResetEvent 检查事件是否已发出信号,请参阅Thread Synchronization (C# Programming Guide) 的示例

    【讨论】:

    • 我可能有错误的一端,但我认为这没有帮助 - 如果工作线程正在执行长时间运行的代码部分,那么它不会拿起 ManualResetEvent,直到它调用 WaitOne ()。
    • 好的,但是你的线程必须一次又一次地检查事件是否已经到达,否则杀死线程是错误的。您的线程设计必须防止正确喊叫并释放所有资源。
    【解决方案6】:

    这是我用于在 Windows 服务中停止线程的代码(请注意,我直接使用线程而不使用线程池):

    // signal all threads to stop
    this.AllThreadsStopSignal.Set();
    
    if (logThreads.IsDebugEnabled)
        logThreads.Debug ("Stop workers");
    
    // remember the time of the signal
    DateTime signalTime = DateTime.Now;
    
    // create an array of workers to be stopped
    List<IServiceWorker> workersToBeStopped = new List<IServiceWorker> (workers);
    
    while (true)
    {
        // wait for some time
        Thread.Sleep (1000);
    
        // go through the list and see if any workers have stopped
        int i = 0;
        while (i < workersToBeStopped.Count)
        {
            IServiceWorker workerToBeStopped = workersToBeStopped [i];
    
            if (log.IsDebugEnabled)
                log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Stopping worker '{0}'. Worker state={1}",
                    workerToBeStopped.WorkerDescription,
                    workerToBeStopped.WorkerState));
    
            bool stopped = workerToBeStopped.JoinThread (TimeSpan.Zero);
    
            // if stopped, remove it from the list
            if (stopped)
            {
                workersToBeStopped.RemoveAt (i);
                if (log.IsDebugEnabled)
                    log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                        "Worker '{0}' was stopped.", workerToBeStopped.WorkerDescription));
            }
            else
            {
                i++;
                if (log.IsDebugEnabled)
                    log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                        "Worker '{0}' could not be stopped, will try again later. Worker state={1}",
                        workerToBeStopped.WorkerDescription,
                        workerToBeStopped.WorkerState));
            }
        }
    
        // if all workers were stopped, exit from the loop
        if (workersToBeStopped.Count == 0)
            break;
    
        // check if the duration of stopping has exceeded maximum time
        DateTime nowTime = DateTime.Now;
        TimeSpan duration = nowTime - signalTime;
    
        if (duration > serviceCustomization.ThreadTerminationMaxDuration)
        {
            // execute forced abortion of all workers which have not stopped
            foreach (IServiceWorker worker in workersToBeStopped)
            {
                try
                {
                    log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                        "Aborting worker '{0}.", worker.WorkerDescription));
                    worker.Abort ();
                    log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                        "Worker '{0}' aborted.", worker.WorkerDescription));
                }
                catch (ThreadStateException ex)
                {
                    log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                        "Worker '{0}' could not be aborted.", worker.WorkerDescription), ex);
                }
            }
            break;
        }
    }
    

    【讨论】:

      【解决方案7】:

      如果您的进程无论如何都被关闭,我个人认为使用 Abort() 没有问题。我会尝试寻找另一种方法,但最终,如果无论如何你都会在主线程中进行清理,这并不重要。

      另一种选择是将您的工作线程标记为background 线程。这样,它们会在进程关闭时自动关闭。您或许可以在退出前使用 AppDomain.ProcessExit 事件进行清理。

      【讨论】:

      • 我建议永远不要使用 Abort。在 .NET 发布两年后,我忽略了这个警告,并因此遭受了一千次削减。可能会在任何地方抛出中止,完全排除任何和所有假设,包括干净的“尝试...最终”行为,或者说,在添加列表逻辑的中途,在这种情况下计数已增加但项目未添加。我唯一可以认为 Abort 没问题的情况是针对没有副作用 (IO) 的进程,它本质上只是一个大型计算引擎,在终止期间可以安全地破坏其内存。
      猜你喜欢
      • 1970-01-01
      • 2012-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-04
      • 1970-01-01
      • 2020-01-23
      • 2011-02-17
      相关资源
      最近更新 更多