在 Windows 服务停止时优雅地关闭前台线程 [英] Close foreground thread gracefully on windows service stop

查看:73
本文介绍了在 Windows 服务停止时优雅地关闭前台线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的 Windows 服务中,我创建了一个父"前台线程,该线程又使用 ThreadPool(这意味着它们是后台)生成子"线程来执行任务.

In my windows service I create one "parent" foreground thread that in turn spawns "child" threads using ThreadPool (which means they are background) to execute tasks.

在 Windows 服务停止时优雅地关闭前台线程的最佳方法是什么?

What is the best way to close foreground thread gracefully on windows service stop?

这是我当前的实现(去除了特定于任务的逻辑):

Here is my current implementation (stripped out of task-specific logic):

public partial class TaskScheduler : ServiceBase
{
   private static AutoResetEvent _finishedTaskAutoResetEvent = new AutoResetEvent(false);

   //This flag is used to increase chances of the Spawning Thread to finish gracefully when service stops.
   private bool StopRequested { get; set; }

   private int _executingTasksCount;

   private int ExecutingTasksCount { get { return _executingTasksCount; } }

   private void IncCurrentTasksCount()
   {
       Interlocked.Increment(ref _executingTasksCount);
   }

   private void DecCurrentTasksCount()
   {
       Interlocked.Decrement(ref _executingTasksCount);
   }

   public TaskScheduler()
   {
       InitializeComponent();

       Thread spawningThread = new Thread(DoSpawnTaskExecutionThreads);

       spawningThread.Name = "Spawning Thread";
       spawningThread.IsBackground = false;
       spawningThread.Start();
   }

   protected override void OnStart(string[] args)
   {
   }

   protected override void OnStop()
   {
       StopRequested = true;
   }

   private void DoSpawnTaskExecutionThreads()
   {
       //We check StopRequested to try and finish this thread gracefully when service stops.
       while (!StopRequested)
       {
           while (!StopRequested && ExecutingTasksCount < MaxPooledTasks)
           {
               ThreadPool.QueueUserWorkItem(ExecuteTask, new Task());

               IncCurrentTasksCount();
           }

           _finishedTaskAutoResetEvent.WaitOne();
       }

       //Either all task execution threads will finish or the process will be terminated forcibly.
       while (ExecutingTasksCount > 0)
       {
           Thread.Sleep(200); //Check five times a second.
       }

       _eventLog.WriteEntry("The Spawning Thread finished along with task execution threads.");
   }

   private void ExecuteTask(object state)
   {
       try
       {
           Task task = (Task)state;

           task.Execute();
       }
       catch
       {
           // Handle exception.
       }
       finally
       {
           DecCurrentTasksCount();
           _finishedTaskAutoResetEvent.Set();
       }
   }

}

推荐答案

我发现代码有几个问题.

I see a couple of problems with the code.

  • StopRequested 的检查不是线程安全的.
  • ExecutingTaskCount 的检查不是线程安全的.
  • 由于 _finishedTaskAutoResetEventAutoResetEvent 信号可能会丢失,因为 WaitHandle 不维护计数.也许这就是您想要的,但它可能会导致嵌套的 while 循环出现一些奇怪的旋转.
  • The check of StopRequested is not thread-safe.
  • The check of ExecutingTaskCount is not thread-safe.
  • Since _finishedTaskAutoResetEvent is an AutoResetEvent signals can get lost because that WaitHandle does not maintain a count. Maybe that is what you want, but it could result in some strange spinning of the nested while loops.

这是我将如何重构您的代码.它使用 .NET 4.0 中可用的 CountdownEvent 类.

Here is how I would refactor your code. It uses the CountdownEvent class which is available in .NET 4.0.

public class TaskScheduler : ServiceBase
{
    private m_Stop as ManualResetEvent = new ManualResetEvent(false);

    protected override void OnStart(string[] args)           
    {           
      var thread = new Thread(DoSpawnTaskExecutionThreads);
      thread.Name = "Spawning Thread";
      thread.IsBackground = false;
      thread.Start();
    }           

    protected override OnStop()
    {
      m_Stop.Set();
    }

    public DoSpawnTaskExecutionThreads()
    {
      // The semaphore will control how many concurrent tasks can run.
      var pool = new Semaphore(MaxPooledThreads, MaxPooledThreads);

      // The countdown event will be used to wait for any pending tasks.
      // Initialize the count to 1 so that we treat this thread as if it 
      // were a work item. This is necessary to avoid a subtle race
      // with a real work item that completes quickly.
      var tasks = new CountdownEvent(1);

      // This array will be used to control the spinning of the loop.
      var all = new WaitHandle[] { pool, m_Stop };

      while (WaitHandle.WaitAny(all) == 0)
      {
        // Indicate that there is another task.
        tasks.AddCount();

        // Queue the task.
        Thread.QueueUserWorkItem(
          (state) =>
          {
            try
            {
              var task = (Task)state;
              task.Execute();
            }
            finally
            {
              pool.Release(); // Allow another task to be queued.
              tasks.Signal(); // Indicate that this task is complete.
            }
          }, new Task());
      }

      // Indicate that the main thread is complete.
      tasks.Signal();

      // Wait for all pending tasks.
      tasks.Wait();
    }
}

这篇关于在 Windows 服务停止时优雅地关闭前台线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆