Изящно закрыть поток переднего плана на остановке службы Windows - PullRequest
3 голосов
/ 25 октября 2010

В моей службе 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 ]

3 голосов
/ 25 октября 2010

Я вижу пару проблем с кодом.

  • Проверка StopRequested не является поточно-ориентированной.
  • Проверка ExecutingTaskCount не является поточнойsafe.
  • Поскольку _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 голосов
/ 25 октября 2010

Здесь я вижу одну проблему:

StopRequested не должен быть автоматическим свойством.Вы должны определить это как свойство с полем поддержки, чтобы пометить его volatile .

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

Без этого возможно, что условие выхода можетне будет виден (по крайней мере, сразу) вашим потоком, когда он установлен службой.

Кроме того, если вариант .NET 4, существует гораздо более простой дизайн, который может быть выполнен с использованием CancellationToken и BlockingCollection<T>.

0 голосов
/ 25 октября 2010

Вы можете использовать метод Join, чтобы "изящно" убить поток. MSDN содержит некоторую информацию о методе.

...