2009-04-28 6 views
5

avoir ce que je suppose est un scénario de filetage assez commun:files d'attente de discussion pour les nuls

  • J'ai 100 emplois identiques pour compléter
  • Tous les emplois sont indépendants les uns autres
  • Je veux traiter un maximum de 15 emplois à temps
  • Comme chaque emploi finalise, un nouvel emploi sera lancé jusqu'à ce que tous les travaux ont été achevés

Si vous supposez que chaque travail déclenchera un événement lorsqu'il terminera (j'utilise la classe BackgroundWorker), je peux penser à quelques façons de le faire, mais je ne suis pas sûr de ce que le " droite "solution est. J'espérais que certains d'entre vous, les gourous, pourraient me pointer dans la bonne direction.

SOLUTION 1: Avez-vous un while (continuez) {Threading.Sleep (1000); } boucle dans ma fonction Main(). Le code dans le gestionnaire d'événements Job_Completed définira continue = false lorsque A) aucun travail reste à être mis en file d'attente et B) tous les travaux en attente ont terminé. J'ai utilisé cette solution avant et pendant qu'il semble fonctionner bien ... il me semble un peu "étrange".

SOLUTION 2: Utilisez Application.Run() dans ma fonction Main(). De même, le code dans le gestionnaire d'événements Job_Completed appelle Application.Exit() lorsque A) aucun travail reste à être mis en file d'attente et B) tous les travaux en attente ont terminé.

SOLUTION 3: Utilisez un ThreadPool, mettez en file d'attente toutes les demandes 500-1000, laissez-les s'exécuter 10 à la fois (SetMaxThreads) et d'une manière ou d'une autre attendez qu'elles soient toutes terminées.

Dans toutes ces solutions, l'idée de base est qu'un nouveau travail est démarré chaque fois qu'un autre travail est terminé, jusqu'à ce qu'il ne reste plus de travail. Ainsi, le problème n'attend pas seulement que les travaux existants soient terminés, mais attend également qu'il n'y ait plus de travail en attente pour démarrer. Si ThreadPool est la bonne solution, quelle est la bonne façon d'attendre sur le ThreadPool pour terminer tous les éléments mis en file d'attente?

Je pense que ma confusion dominante ici est que je ne comprends pas exactement comment les événements peuvent se déclencher à partir de ma fonction Main(). Apparemment ils le font, je ne comprends tout simplement pas la mécanique d'un point de vue de la boucle de message Windows. Quelle est la bonne façon de résoudre ce problème, et pourquoi?

+0

Il semble que la plupart des suggestions s'articulent autour d'une solution de style ThreadPool ... qu'en est-il de ma solution SOLUTION 1 et SOLUTION 2 ci-dessus (essentiellement en attente d'une condition à modifier par un événement)? Y a-t-il quelque chose d'intrinsèquement mauvais à faire ainsi, ou est-ce juste que .NET fournit ThreadPool donc ce n'est pas nécessaire? Il semble étrange d'avoir un code comme celui-ci: while (continuez) Threading.Sleep (1000); ... en attendant que les événements se déclenchent dans la fonction Main(). Dans un code comme celui-ci ... quand exactement mes événements sont-ils traités ... quelque part dans l'appel Sleep()? –

Répondre

0

Voici le pseudo-code de la façon dont je l'aborder (cela ne pas tirer parti ThreadPool, donc quelqu'un pourrait avoir une meilleure réponse :)

main 
{ 
    create queue of 100 jobs 
    create new array of 15 threads 
    start threads, passing each the job queue 
    do whatever until threads are done 
} 

thread(queue) 
{ 
    while(queue isn't empty) 
    { 
     lock(queue) { if queue still isn't empty dequeue a thing } 
     process the thing 
    } 

    queue is empty so exit thread 
} 

EDIT: Si votre question est de savoir comment dire quand les fils sont terminé, et que vous utilisez des threads C# normaux (pas des threads ThreadPooled), vous pouvez appeler Thread.Join() sur chaque thread avec un délai facultatif et il ne retournera qu'une fois le thread terminé.Si vous voulez garder une trace de combien de threads sont fait sans enferrons sur un, vous pouvez faire défiler eux d'une manière comme ceci:

for(int i = 0; allThreads.Count > 0; i++) 
{ 
    var thisThread = allThreads[i % threads.Count]; 
    if(thisThread.Join(timeout)) // something low, maybe 100 ms or something 
     allThreads.Remove(thisThread); 
} 
2

Re: « attendre en quelque sorte pour eux tous pour terminer »

ManualResetEvent est votre ami, avant de commencer votre grand lot créer un de ces chiots, dans votre thread principal attendez-le, réglez-le à la fin de l'opération en arrière-plan lorsque le travail est terminé.

Une autre option est de créer manuellement les fils et faire un fil de foreach, Thread.Join()

Vous pouvez utiliser cette (j'utiliser ce au cours des essais)

 private void Repeat(int times, int asyncThreads, Action action, Action done) { 
     if (asyncThreads > 0) { 

      var threads = new List<Thread>(); 

      for (int i = 0; i < asyncThreads; i++) { 

       int iterations = times/asyncThreads; 
       if (i == 0) { 
        iterations += times % asyncThreads;      
       } 

       Thread thread = new Thread(new ThreadStart(() => Repeat(iterations, 0, action, null))); 
       thread.Start(); 
       threads.Add(thread); 
      } 

      foreach (var thread in threads) { 
       thread.Join(); 
      } 

     } else { 
      for (int i = 0; i < times; i++) { 
       action(); 
      } 
     } 
     if (done != null) { 
      done(); 
     } 
    } 

Utilisation:

// Do something 100 times in 15 background threads, wait for them all to finish. 
Repeat(100, 15, DoSomething, null) 
1

Je voudrais juste utiliser la bibliothèque parallèle de tâches.

Vous pouvez le faire en tant que simple boucle Parallel.For simple avec vos tâches, et il gérera automatiquement cette opération de manière assez propre. Si vous ne pouvez pas attendre l'implémentation de C# 4 et de Microsoft, une solution de contournement temporaire consiste simplement à compiler et à utiliser le Mono Implementation of TPL. (Personnellement, je préfère l'implémentation MS, en particulier les nouvelles versions bêta, mais la version mono est fonctionnelle et redistribuable aujourd'hui.)

0

Lorsque vous mettez en file d'attente un élément de travail dans la file d'attente de threads, vous devez récupérer un waitghle. Placez-les tous dans un tableau et vous pouvez le passer en argument à la fonction WaitAll().

+0

Bonne idée, mais comment feriez-vous cela? QueueUserWorkItem renvoie un booléen. – Grokys

1

J'utiliserais ThreadPool.

Avant de commencer à exécuter vos travaux, créez un ManualResetEvent et un compteur int. Ajoutez chaque travail au ThreadPool en incrémentant le compteur à chaque fois.

À la fin de chaque travail, décrémentez le compteur et, lorsqu'il atteint zéro, appelez Set() sur l'événement.

Dans votre thread principal, appelez WaitOne() pour attendre que tous les travaux soient terminés.

3

Même si les autres réponses sont bien si vous voulez une autre option (vous ne pouvez jamais avoir assez d'options), alors que diriez-vous comme une idée.

Il suffit de placer les données de chaque travail dans une structure qui se trouve dans une pile FIFO.

Créez 15 discussions.

Chaque thread récupère le travail suivant de la pile.

Lorsqu'un thread termine le traitement, passez au travail suivant, si la pile est vide, le thread meurt ou dort simplement, en attente.

La seule complexité, qui est assez simple à résoudre, est d'avoir le popping dans une section critique (synchroniser read/pop).

0

ThreadPool peut être le chemin à parcourir. La méthode SetMaxThreads serait capable de limiter le nombre de threads en cours d'exécution. Toutefois, cela limite le nombre maximal de threads pour le processus/AppDomain.Je ne suggérerais pas d'utiliser SetMaxThreads si le processus s'exécute en tant que service.

private static ManualResetEvent manual = new ManualResetEvent(false); 
private static int count = 0; 

public void RunJobs(List<JobState> states) 
{ 
    ThreadPool.SetMaxThreads(15, 15); 

    foreach(var state in states) 
    { 
      Interlocked.Increment(count); 
      ThreadPool.QueueUserWorkItem(Job, state); 
    } 

    manual.WaitOne(); 
} 

private static void Job(object state) 
{ 
    // run job 
    Interlocked.Decrement(count); 
    if(Interlocked.Read(count) == 0) manual.Set(); 
} 
0

cadre réactive Microsoft est superbe pour cela:

Action[] jobs = new Action[100]; 

var subscription = 
    jobs 
     .ToObservable() 
     .Select(job => Observable.Start(job)) 
     .Merge(15) 
     .Subscribe(
      x => Console.WriteLine("Job Done."), 
      () => Console.WriteLine("All Jobs Done.")) 

Terminé.

Juste NuGet "System.Reactive".

Questions connexes