代码之家  ›  专栏  ›  技术社区  ›  Den

在windows服务停止时优雅地关闭前台线程

  •  3
  • Den  · 技术社区  · 15 年前

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

    3 回复  |  直到 15 年前
        1
  •  3
  •   Brian Gideon    15 年前

    我看到代码有几个问题。

    • 支票 StopRequested 不是线程安全的。
    • 支票 ExecutingTaskCount
    • 自从 _finishedTaskAutoResetEvent AutoResetEvent 信号可能会丢失,因为 WaitHandle 不保持计数。也许这就是你想要的,但它可能会导致嵌套 while 循环。

    CountdownEvent 在.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();
        }
    }
    
        2
  •  2
  •   Brian Gideon    15 年前

    我在这里看到一个问题:

    StopRequired不应是自动属性。您应该将其定义为带有后备字段的属性,以便对其进行标记 volatile

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

    如果没有这一点,那么当服务设置退出条件时,您的线程可能看不到它(至少现在是这样)。

    另外,如果.NET 4是一个选项,那么可以使用 CancellationToken BlockingCollection<T> .

        3
  •  0
  •   Pedro    15 年前

    MSDN 有一些关于这个方法的信息。

    推荐文章