Close foreground thread gracefully on windows service stop

给你一囗甜甜゛ 提交于 2019-12-04 14:10:31

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!