问题
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 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();
}
}
}
回答1:
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 anAutoResetEvent
signals can get lost because thatWaitHandle
does not maintain a count. Maybe that is what you want, but it could result in some strange spinning of the nestedwhile
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();
}
}
回答2:
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>
.
回答3:
You can use the Join method to "gracefully" kill the thread. MSDN has some information about the method.
来源:https://stackoverflow.com/questions/4016239/close-foreground-thread-gracefully-on-windows-service-stop