2010-10-25 54 views
3

在我的Windows服务中,我创建了一个“父”前台线程,它使用ThreadPool(这意味着它们是后台)产生执行任务的“子”线程。在windows服务上优雅地关闭前台线程

在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

我看到了一些代码问题。

  • StopRequested的检查不是线程安全的。
  • ExecutingTaskCount的检查不是线程安全的。
  • 由于_finishedTaskAutoResetEvent是一个AutoResetEvent信号可能会丢失,因为该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(); 
    } 
} 
+0

感谢您提供如此详尽的解释和示例代码。我有一些问题: 1)“ExecutingTaskCount的检查不是线程安全的”:为什么?我只是用Interlocked类修改它。如果因为某种原因我仍然想使用它,我会怎么做呢? 你什么时候会推荐使用Interlocked类? 2)“... _finishedTaskAutoResetEvent是一个AutoResetEvent信号可能会因为WaitHandle不保持计数而丢失......”:这种情况的原因是什么?任务抛出一个未处理的异常,我因为某种原因没有处理它? – Den 2010-10-26 15:39:25

+0

RE#1 ...为了让'ExecutingTasksCount'线程安全,您将必须执行'_executingTasksCount'的volatile读取。这可以使用'Interlocked.CompareExchange'方法或将变量标记为'volatile'来完成。 – 2010-10-26 17:58:43

+0

RE#2 ...想象一下在DecCurrentTasksCount和_finishedTaskAutoResetEvent.Set之间所有任务线程都被抢占的假想场景(非常不可能)。我认为你的嵌套循环可以防范任何问题,但我想象的是他们可以表现的奇怪方式。再次,我认为这种方法实际上没有任何问题,但很难去思考。 – 2010-10-26 18:10:44

2

有一个问题,我在这里看到:

StopRequested不应该是一个自动财产。您应该将其定义为具有后台字段的属性,以便将其标记为​​。

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

没有这一点,这是可能的退出条件,可能无法察觉(至少就)通过你的线程时,它是由服务设置。另外,如果.NET 4是一个选项,那么使用CancellationTokenBlockingCollection<T>可以完成更简单的设计。

+0

我打算改变StopRequested的唯一地方是OnStop()。感谢您的建议。 – Den 2010-10-25 15:54:27

+2

@Den:OnStop将从单独的线程中调用。如果没有这个,TaskScheduler的线程将不会(必然)看到更改。 – 2010-10-25 16:15:09

+1

@Den:基本上会发生的是,JIT可能会看到'DoSpawnTaskExecutionThreads'永远不会更改'StopRequested',因此它可能会尝试通过提升它们并将它们合并到外部和上面的循环中来优化重复读取。循环中的'WaitOne'调用将停止编译器实际上进行优化,但这是偶然发生的情况。这里最好遵循里德的建议,这是毫无疑问的。 – 2010-10-26 18:16:06

0

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

+1

这在这里不起作用 - 你不能阻塞服务线程,否则服务主机会强行终止它,因为在关闭服务时存在超时。 – 2010-10-25 15:54:03