【问题标题】:Close foreground thread gracefully on windows service stop在 Windows 服务停止时优雅地关闭前台线程
【发布时间】:2011-04-30 07:07:07
【问题描述】:

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

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

这是我当前的实现(从特定于任务的逻辑中剥离出来):

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();
       }
   }

}

【问题讨论】:

    标签: .net multithreading windows-services threadpool foreground


    【解决方案1】:

    我发现代码存在一些问题。

    • StopRequested 的检查不是线程安全的。
    • ExecutingTaskCount 的检查不是线程安全的。
    • 由于_finishedTaskAutoResetEventAutoResetEvent,信号可能会丢失,因为WaitHandle 不保持计数。也许这就是你想要的,但它可能会导致嵌套的 while 循环出现一些奇怪的旋转。

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

    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();
        }
    }
    

    【讨论】:

    • 感谢您提供如此详尽的解释和示例代码。不过我有一些问题:1)“ExecutingTaskCount 的检查不是线程安全的”:为什么?我只是使用 Interlocked 类对其进行修改。如果出于某种原因我仍然想使用它,我会怎么做?您什么时候推荐使用 Interlocked 类? 2)“...... _finishedTaskAutoResetEvent 是一个 AutoResetEvent 信号可能会丢失,因为 WaitHandle 不维护计数......”:这种情况的原因可能是什么?任务抛出未处理的异常而我由于某种原因没有处理它?
    • RE #1...要使ExecutingTasksCount 线程安全,您必须对_executingTasksCount 进行易失性读取。这可以使用Interlocked.CompareExchange 方法或将变量标记为volatile 来完成。
    • RE #2...想象一个假设场景(这不太可能),所有任务线程在DecCurrentTasksCount_finishedTaskAutoResetEvent.Set 之间被抢占。我认为您的嵌套循环可以防止任何问题,但我正在设想它们可以表现的奇怪方式。同样,我认为这种方法实际上没有任何问题,但很难考虑。
    【解决方案2】:

    我在这里看到一个问题:

    StopRequested 不应是自动属性。您应该将此定义为具有支持字段的属性,以便将其标记为volatile

    private volatile bool stopRequested;
    private bool StopRequested
    {
        get { return this.stopRequested; }
        set { this.stopRequested = value; }
    }
    

    如果没有这个,当退出条件由服务设置时,您的线程可能看不到(至少马上)退出条件。

    此外,如果 .NET 4 是一个选项,则可以使用 CancellationTokenBlockingCollection&lt;T&gt; 完成更简单的设计。

    【讨论】:

    • 我打算更改 StopRequested 的唯一地方是 OnStop()。谢谢你的建议。
    • @Den:将从单独的线程调用 OnStop。没有这个,TaskScheduler 的线程将不会(必然)看到变化。
    • @Den:基本上会发生的是 JIT 可能会看到 DoSpawnTaskExecutionThreads 永远不会改变 StopRequested,因此它可能会尝试通过提升重复读取并将它们组合成一个外部和在循环之上。您在循环中的WaitOne 调用将阻止编译器在现实中进行优化,但这是它意外工作的情况。最好在这里听从里德的建议,这样就没有疑问了。
    【解决方案3】:

    您可以使用 Join 方法“优雅地”终止线程。 MSDN 有一些关于方法的信息。

    【讨论】:

    • 这里不行——你不能阻塞服务线程,否则服务主机会强行杀死它,因为关闭服务有超时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-10
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多