Dans mon service Windows, je crée un thread de premier plan "parent" qui génère à son tour des threads "enfants" en utilisant ThreadPool (ce qui signifie qu'ils sont en arrière-plan) pour exécuter des tâches.Fermer le thread de premier plan gracieusement sur Windows service stop
Quelle est la meilleure façon de fermer le fil d'avant-plan gracieusement sur l'arrêt de service des fenêtres?
Voici mon implémentation actuelle (dépouillé de la logique spécifique à la tâche):
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();
}
}
}
Merci pour une explication si complète et un exemple de code. J'ai quelques questions cependant: 1) "La vérification de ExecutingTaskCount n'est pas thread-safe": Pourquoi? Je ne fais que le modifier en utilisant la classe Interlocked. Si je voulais toujours l'utiliser pour une raison quelconque, comment pourrais-je y aller? Quand recommanderiez-vous d'utiliser la classe Interlocked? 2) "... _finishedTaskAutoResetEvent est un AutoResetEvent les signaux peuvent être perdus parce que WaitHandle ne maintient pas un compte ...": Quelle pourrait être la cause d'une telle situation? Tâche de lancer une exception non gérée et de ne pas la gérer pour une raison quelconque? – Den
RE # 1 ... Pour rendre thread-safe 'ExecutingTasksCount', vous devrez faire une lecture volatile de '_executingTasksCount'. Cela peut être fait en utilisant la méthode 'Interlocked.CompareExchange' ou en marquant la variable comme' volatile'. –
RE # 2 ... Imaginez un scénario hypothétique (très improbable) où tous les threads de tâche sont préemptés entre 'DecCurrentTasksCount' et' _finishedTaskAutoResetEvent.Set'. Je pense que vos boucles imbriquées protègent contre tout problème, mais je vois des façons bizarres de se comporter. Encore une fois, je ne pense pas qu'il y ait vraiment de problème avec cette approche, mais il est difficile d'y penser. –