开发者

Close foreground thread gracefully on windows service stop

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.

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 Auto开发者_高级运维ResetEvent _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.

  • 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.

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


There is one issues I see here:

StopRequested should not be an automatic property. You should define this as a property with a backing field, in order to mark it volatile.

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

Without this, it's possible that the exit condition may not be seen (at least right away) by your thread when it's set by the service.

Also, if .NET 4 is an option, there are much simpler designed that could be done using CancellationToken and BlockingCollection<T>.


You can use the Join method to "gracefully" kill the thread. MSDN has some information about the method.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜