2010-02-02 5 views
2

Je suis à la recherche d'une bonne méthode de suivi (compter) quels travailleurs ont échoué lorsqu'ils sont mis en file d'attente avec un Threadpool et en utilisant WaitHandle.WaitAll() pour tous les threads à terminer.Une méthode robuste de suivi des travailleurs échoués avec ThreadPool

L'interverrouillage d'un compteur est-il une bonne technique ou existe-t-il une stratégie plus robuste?

+0

Comment envisagez-vous les fils de rapports leur statut? Serait-ce correct s'ils se contentent de mettre un 'bool' à' true' ou 'false'? – jason

+0

Je pensais que la méthode de mise en file d'attente aurait un compteur statique –

+0

Je ne suis pas; il semble qu'un compteur ne soit utile que pour vous dire combien de travailleurs ont réussi ou échoué, mais pas quels travailleurs ont réussi ou échoué. Qu'est-ce que je rate? – jason

Répondre

1

Bon, voici une approche que vous pourriez prendre. J'ai encapsulé les données que nous voulons suivre dans une classe TrackedWorkers. Il existe un constructeur sur cette classe qui vous permet de définir combien de travailleurs vont travailler. Ensuite, les travailleurs sont lancés en utilisant LaunchWorkers, ce qui nécessite un délégué qui mange un object et renvoie un bool. Le object représente l'entrée pour le travailleur et le bool représente le succès ou l'échec en fonction de true ou false étant la valeur de retour, respectivement.

Donc, fondamentalement, ce que nous faisons, nous avons un tableau pour suivre l'état des travailleurs. Nous lançons les travailleurs et définissons le statut correspondant à ce travailleur en fonction de la valeur de retour du travailleur. Lorsque le travailleur revient, nous définissons un AutoResetEvent et un WaitHandle.WaitAll pour tous les AutoResetEvents à définir. Notez qu'il existe une classe imbriquée pour suivre le travail (le délégué) que le worker doit effectuer, l'entrée de ce travail et une ID utilisée pour définir le statut AutoResetEvent correspondant à ce thread.

Notez très soigneusement qu'une fois le travail terminé, nous ne détenons pas de référence au délégué de travail func ni au input. Ceci est important afin que nous n'empêchions pas accidentellement des choses d'être collectées.

Il existe des méthodes permettant d'obtenir le statut d'un worker particulier, ainsi que tous les index des workers qui ont réussi et tous les index des workers qui ont échoué.

Une dernière remarque: Je ne considère pas que cette production de code soit prête. Ce n'est qu'une esquisse de l'approche que je prendrais. Vous devez prendre soin d'ajouter des tests, la gestion des exceptions et d'autres détails.

class TrackedWorkers { 
    class WorkerState { 
     public object Input { get; private set; } 
     public int ID { get; private set; } 
     public Func<object, bool> Func { get; private set; } 
     public WorkerState(Func<object, bool> func, object input, int id) { 
      Func = func; 
      Input = input; 
      ID = id; 
     } 
    } 

    AutoResetEvent[] events; 
    bool[] statuses; 
    bool _workComplete; 
    int _number; 

    public TrackedWorkers(int number) { 
     if (number <= 0 || number > 64) { 
      throw new ArgumentOutOfRangeException(
       "number", 
       "number must be positive and at most 64" 
      ); 
     } 
     this._number = number; 
     events = new AutoResetEvent[number]; 
     statuses = new bool[number]; 
     _workComplete = false; 
    } 

    void Initialize() { 
     _workComplete = false; 
     for (int i = 0; i < _number; i++) { 
      events[i] = new AutoResetEvent(false); 
      statuses[i] = true; 
     } 
    } 

    void DoWork(object state) { 
     WorkerState ws = (WorkerState)state; 
     statuses[ws.ID] = ws.Func(ws.Input); 
     events[ws.ID].Set(); 
    } 

    public void LaunchWorkers(Func<object, bool> func, object[] inputs) { 
     Initialize(); 
     for (int i = 0; i < _number; i++) { 
      WorkerState ws = new WorkerState(func, inputs[i], i); 
      ThreadPool.QueueUserWorkItem(this.DoWork, ws); 
     } 
     WaitHandle.WaitAll(events); 
     _workComplete = true; 
    } 

    void ThrowIfWorkIsNotDone() { 
     if (!_workComplete) { 
      throw new InvalidOperationException("work not complete"); 
     } 
    } 

    public bool GetWorkerStatus(int i) { 
     ThrowIfWorkIsNotDone(); 
     return statuses[i]; 
    } 

    public IEnumerable<int> SuccessfulWorkers { 
     get { 
      return WorkersWhere(b => b); 
     } 
    } 

    public IEnumerable<int> FailedWorkers { 
     get { 
      return WorkersWhere(b => !b); 
     } 
    } 

    IEnumerable<int> WorkersWhere(Predicate<bool> predicate) { 
     ThrowIfWorkIsNotDone(); 
     for (int i = 0; i < _number; i++) { 
      if (predicate(statuses[i])) { 
       yield return i; 
      } 
     } 
    } 
} 

utilisation de l'échantillon:

class Program { 
    static Random rg = new Random(); 
    static object lockObject = new object(); 
    static void Main(string[] args) { 
     int count = 64; 
     Pair[] pairs = new Pair[count]; 
     for(int i = 0; i < count; i++) { 
      pairs[i] = new Pair(i, 2 * i); 
     } 
     TrackedWorkers workers = new TrackedWorkers(count); 
     workers.LaunchWorkers(SleepAndAdd, pairs.Cast<object>().ToArray()); 
     Console.WriteLine(
      "Number successful: {0}", 
      workers.SuccessfulWorkers.Count() 
     ); 
     Console.WriteLine(
      "Number failed: {0}", 
      workers.FailedWorkers.Count() 
     ); 
    } 
    static bool SleepAndAdd(object o) { 
     Pair pair = (Pair)o; 
     int timeout; 
     double d; 
     lock (lockObject) { 
      timeout = rg.Next(1000); 
      d = rg.NextDouble(); 
     } 
     Thread.Sleep(timeout); 
     bool success = d < 0.5; 
     if (success) { 
      Console.WriteLine(pair.First + pair.Second); 
     } 
     return (success); 

    } 
} 

Le programme ci-dessus va lancer soixante-quatre fils. Le i th thread a pour tâche d'ajouter les numéros i et 2 * i et d'imprimer le résultat à la console. Cependant, j'ai ajouté une quantité aléatoire de sommeil (moins d'une seconde) pour simuler l'activité et je retourne une pièce de monnaie pour déterminer le succès ou l'échec du fil. Ceux qui réussissent impriment la somme à laquelle ils ont été assignés et renvoient true. Ceux qui échouent n'impriment rien et retournent false.

Ici, je l'ai utilisé

struct Pair { 
    public int First { get; private set; } 
    public int Second { get; private set; } 
    public Pair(int first, int second) : this() { 
     this.First = first; 
     this.Second = second; 
    } 
} 
+0

+5 pour votre effort. Je pense que j'ai peut-être mal formulé la question ou j'aurais plus de réponses. Une modification mineure est délégués plutôt que lambda –

+0

Je vais reformuler cela comme lambda est bien. Les événements sont-ils une mauvaise alternative? Je comprends la limite WorkItem, mais y a-t-il une limite concernant le nombre d'éléments que vous mettez dans le pool de threads? Désolé si je suis en train de transformer ceci en 2 autres questions –

+0

@Chris S: Eh bien, la question était un peu floue; vous avez laissé quelques choses non spécifiées. Il est utile de poser des questions aussi précises que possible, sans être pédant, bien sûr. Cela dit, nous avons éclairci quelques points dans les commentaires à votre question. Qu'entendez-vous par "les événements sont-ils une mauvaise alternative"? En tant que mécanisme de signalisation? En ce qui concerne la limite, il y a une limite au nombre de 'AutoResetEvent' qui peut être attendu. Si vous avez besoin de plus de threads, vous devrez faire un peu de travail pour les partager entre plusieurs collections de 'AutoResetEvent'. – jason