2017-06-24 1 views
1

J'ai vu plusieurs messages sur SO qui sont similaires à ma question, mais aucun d'entre eux n'a résolu mon problème. Je crée un service Windows qui va interroger une base de données Redis toutes les quelques secondes et effectuer une action basée sur le résultat. Je voudrais créer un "pool de threads" de sorte que je puisse exécuter plusieurs actions en même temps si j'obtiens un résultat de Redis pendant qu'une autre commande est en cours de traitement (sur un autre thread).Boucle infinie en utilisant des threads dans Windows Service

L'un de mes principaux problèmes est que lorsque j'arrêter mon service Windows, le processus reste encore en vie à 30 secondes ou si au lieu de fermer. Voici les extraits de code pertinents:

Thread Worker; 
IDatabase db = ...; 
AutoResetEvent StopRequest = new AutoResetEvent(false); 

protected override void OnStart(string[] args) { 
    var poller = new Poller(); 
    Worker = new Thread(() => poller.Poll(StopRequest)); 
    Worker.Start(); 
} 
protected override void OnStop() { 
    // Signal worker to stop and wait until it does 
    StopRequest.Set(); 
    Worker.Join(); 
} 

Voici un exemple des Poller classes de méthode Poll.

public async void Poll(AutoResetEvent finished) 
{ 
    var res = string.Empty; 

    while (!finished.WaitOne(1000)) 
    { 
     res = db.StringGet($"task"); 
     if (!String.IsNullOrEmpty(res)) 
     { 
      ParseAction(res); 
     } 

     db.KeyDelete($"task"); 
    } 
} 

donc ce code (avec beaucoup coupé sur) reste en cours d'exécution en arrière-plan correctement et semble traiter les requêtes entrantes de Redis très bien, mais je vais avoir le problème avec le processus ne ferme pas correctement que je mentionné ci-dessus. Je ne suis pas sûr que ce soit la meilleure approche à adopter pour cette situation. J'aimerais des pointeurs sur des façons meilleures ou plus «idiomatiques» de gérer ce problème de threading.

Merci!

Répondre

1

Une meilleure façon de traiter avec le service Windows est de déplacer le traitement entier en tâche de fond. Cela vous permettra de gérer le démarrage et l'arrêt beaucoup plus gracieusement.

Et si vous utilisez la tâche pour simuler l'interrogation, vous pouvez utiliser CancellationToken pour propager événement d'arrêt à d'autres couches de traitement. Ici vous pouvez trouver comment simuler la minuterie en utilisant la tâche.Veuillez lire Is there a Task based replacement for System.Threading.Timer?

Voici l'exemple de code des gestionnaires OnStart et OnStop du service Windows avec une tâche d'arrière-plan qui démarre et s'arrête rapidement. Ce code est basé sur .NET 4.6.1.

using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Reflection; 
using System.Threading; 
using System.Threading.Tasks; 
using System.ServiceProcess; 

namespace TEST.MY.SERVICE 
{ 
    partial class MyService : ServiceBase 
    { 
     private Task _initializationTask; 
     private CancellationTokenSource _initializationCancelTokenSource; 
     private CancellationToken _intitializationCancellationToken; 

     public MyService() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnStart(string[] args) 
     { 
     _initializationCancelTokenSource = new CancellationTokenSource(); 
     _intitializationCancellationToken = _initializationCancelTokenSource.Token; 
     _initializationTask = Task.Run(() => 
     { 
      //Kick off polling from here that also uses _intitializationCancellationToken, so that when _initializationCancelTokenSource.Cancel() is invoked from OnStop it will start cancellation chain reaction to stop all running activity. You can pass it even into your methods and check _intitializationCancellationToken.IsCancellationRequested and take appropriate actions. 

       //using the Task timer from the other stack overflow post, You could do something like 
       Task perdiodicTask = PeriodicTaskFactory.Start(() => 
       { 
        Console.WriteLine(DateTime.Now); 
        //execute your logic here that has to run periodically 
       }, intervalInMilliseconds: 5000, // fire every 5 seconds... 
        cancelToken: _intitializationCancellationToken); // Using same cancellation token to manage timer cancellation 

       perdiodicTask.ContinueWith(_ => 
       { 
        Console.WriteLine("Finished!"); 
       }).Wait(); 

     }, _intitializationCancellationToken) 
     .ContinueWith(t => 
     { 
      //deal with any task related errors 
     },TaskContinuationOptions.OnlyOnFaulted); 
     } 

     protected override void OnStop() 
     { 
     try 
     { 
      _initializationCancelTokenSource?.Cancel(); 
      _initializationCancelTokenSource?.Dispose(); 
      _initializationTask?.Dispose(); 
      } 
      catch (Exception stopException) 
      { 
        //log any errors 
      } 
     } 
    } 
} 

Ici vous pouvez trouver plus de détails sur la façon d'annuler une tâche en attente. https://msdn.microsoft.com/en-us/library/dd321315(v=vs.110).aspx

Cela devrait vous donner une bonne idée sur la façon de concevoir votre service Windows. Faites les tweeks nécessaires pour vos besoins. Familiarisez-vous avec la bibliothèque de tâches C#.

-1

avez-vous réfléchi à l'aide d'un booléen/indicateur binaire pour savoir si le service est en fait en cours d'exécution? ou peut-être effectuer un appel dans le début de la boucle pour vérifier? Je ne suis pas assez familier avec C# pour bien comprendre l'ensemble de la tâche, mais je sais que lorsque Multi-Threading est impliqué, les drapeaux binaires/booléens sont en moyenne plutôt stables. Par exemple, je joue à un Steam Game en bêta (Space Engineers) qui utilise C# et il semble avoir régulièrement des problèmes avec les erreurs Multi-Threading et effacer les données parents après chaque exécution, mais les auteurs de Mod sur Steam Workshop Avoir une tendance à utiliser des drapeaux booléens et binaires afin de s'assurer que leurs tâches ne restent pas coincées ou ne se bloquent pas car les temps de chargement pour relancer le jeu sont horribles, alors ils essaient d'éviter autant de plantages que possible.

Il pourrait ne pas être de fantaisie, mais aussi longtemps que vous assurer qu'il ne crée pas une fuite de mémoire emballement, vous devriez être très bien. Je recommande, si vous utilisez une variable incrémentale pour votre identifiant unique pour chaque tâche, de définir explicitement une limite supérieure quelque part, et lorsque cette limite est atteinte, elle appellera cette tâche et réinitialisera la variable incrémentielle à zéro (avec beaucoup d'espace tampon pour empêcher la perte accidentelle de données). Si la tâche est en cours d'exécution, elle effectuera l'appel, définira le booléen et exécutera, pourrait souhaiter un autre appel pour vérifier que la tâche est toujours en cours avant d'essayer d'écrire sur la destination, car je suppose que sans la tâche , l'information ne fait rien, et si la tâche n'est pas en cours, elle va plonger dans le if != isRunning et être envoyée à la bonne destination pour tuer le fil. J'espère que cette information vous sera utile, comme je l'ai déjà mentionné, je ne suis qu'un débutant en C#, donc je ne suis pas aussi familier avec les commandes avancées que certains des autres utilisateurs ici.