2010-10-25 111 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