web-dev-qa-db-fra.com

Générez plusieurs threads pour le travail, puis attendez que tout soit terminé

je veux juste quelques conseils sur les "meilleures pratiques" concernant les tâches multi-thread.

à titre d'exemple, nous avons une application C # qui, au démarrage, lit les données de diverses tables de "type" dans notre base de données et stocke les informations dans une collection que nous transmettons à l'application. cela nous empêche de frapper la base de données chaque fois que ces informations sont nécessaires.

au moment où l'application lit les données de 10 tables de manière synchrone. J'aimerais vraiment que l'application soit lue à partir de chaque table dans un thread différent fonctionnant tous en parallèle. l'application attendrait que tous les threads se terminent avant de poursuivre le démarrage de l'application.

j'ai examiné BackGroundWorker mais je veux juste quelques conseils pour accomplir ce qui précède.

  1. La méthode semble-t-elle logique afin d'accélérer le temps de démarrage de notre application
  2. Comment pouvons-nous gérer au mieux tous les threads en gardant à l'esprit que le travail de chaque thread est indépendant les uns des autres, nous avons juste besoin d'attendre que tous les threads se terminent avant de continuer.

j'attends avec impatience quelques réponses

53
pharoc

Ma préférence pour cela est de gérer cela via un seul WaitHandle et d'utiliser Interlocked pour éviter de verrouiller sur un compteur:

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        ManualResetEvent resetEvent = new ManualResetEvent(false);
        int toProcess = numThreads;

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                    resetEvent.Set();
            }).Start();
        }

        // Wait for workers.
        resetEvent.WaitOne();
        Console.WriteLine("Finished.");
    }
}

Cela fonctionne bien et s'adapte à n'importe quel nombre de threads de traitement, sans introduire de verrouillage.

91
Reed Copsey

J'aime la solution de @ Reed. Une autre façon d'accomplir la même chose dans .NET 4.0 serait d'utiliser un CountdownEvent .

class Program
{
    static void Main(string[] args)
    {
        var numThreads = 10;
        var countdownEvent = new CountdownEvent(numThreads);

        // Start workers.
        for (var i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // Signal the CountdownEvent.
                countdownEvent.Signal();
            }).Start();
        }

        // Wait for workers.
        countdownEvent.Wait();
        Console.WriteLine("Finished.");
    }
}
28
Jack Leitch

Si vous avez plus de 64 poignées d'attente pour un thread STA comme le dit Mark. vous pouvez créer une liste avec vos threads et attendre que tout se termine dans une seconde boucle.

//check that all threads have completed.
foreach (Thread thread in threadList)
{
     thread.Join();

}  
9
Benjamin Ortuzar

Si vous n'êtes pas sur .NET 4.0, vous pouvez utiliser une liste < ManualResetEvent >, une pour chaque thread et Wait pour qu'ils soient Set =. Pour attendre sur plusieurs threads, vous pouvez envisager d'utiliser WaitAll mais faites attention à la limite de 64 poignées d'attente. Si vous avez besoin de plus que cela, vous pouvez simplement les parcourir et attendre chacun individuellement.

Si vous voulez une expérience de démarrage plus rapide, vous n'avez probablement pas besoin d'attendre que toutes les données soient lues au démarrage. Affichez simplement l'interface graphique et toutes les informations manquantes peuvent être affichées en grisé avec une sorte d'icône "Mise à jour ..." ou similaire. Lorsque les informations arrivent, déclenchez simplement un événement pour mettre à jour l'interface graphique. Il peut y avoir de nombreuses opérations que l'utilisateur peut commencer à effectuer avant même que toutes les données de toutes les tables ne soient lues.

7
Mark Byers

Si vous vous sentez aventureux, vous pouvez utiliser C # 4.0 et la bibliothèque parallèle de tâches:

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});
6
Benjamin Ortuzar

Voici deux modèles d'attente pour plusieurs opérations parallèles. L'astuce est que vous devez également traiter votre thread principal comme s'il s'agissait d'une des opérations parallèles. Sinon, il existe une condition de concurrence subtile entre la signalisation de l'achèvement dans les threads de travail et l'attente de ce signal par le thread principal.

int threadCount = 1;
ManualResetEvent finished = new ManualResetEvent(false);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  Interlocked.Increment(ref threadCount); 
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
          if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
      } 
  }); 
}
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
finished.WaitOne(); 

Par préférence personnelle, j'aime utiliser la classe CountdownEvent pour faire le comptage pour moi qui est disponible dans .NET 4.0.

var finished = new CountdownEvent(1);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  finished.AddCount();
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
        finished.Signal();
      } 
  }); 
}
finished.Signal();
finished.Wait(); 

Les exemples ci-dessus utilisent le ThreadPool, mais vous pouvez l'échanger contre le mécanisme de threading que vous préférez.

4
Brian Gideon

Une autre possibilité avec TPL , en supposant que jobs est la collection d'éléments à traiter ou les sous-threads à exécuter:

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());
2
BartoszKP

Juste pour le plaisir, ce que @Reed a fait avec Monitor. : P

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        int toProcess = numThreads;
        object syncRoot = new object();

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                {
                    lock (syncRoot)
                    {
                        Monitor.Pulse(syncRoot);
                    }
                }
            }).Start();
        }

        // Wait for workers.
        lock (syncRoot)
        {
            if (toProcess > 0)
            {
                Monitor.Wait(syncRoot);
            }
        }

        Console.WriteLine("Finished.");
    }
}
2
Andras Vass

En supposant que les threads du lecteur de base de données reviennent dès qu'ils sont terminés, vous pouvez simplement appeler Thread.Join à tour de rôle sur les dix threads à partir du thread initiateur.

1
Marcelo Cantos

Si vous utilisez .NET 3.5 ou inférieur, vous pouvez utiliser un tableau d'AsyncResult ou BackgroundWorker et compter le nombre de threads retournés (n'oubliez pas de diminuer le compteur avec des opérations interverrouillées) (voir http: // www .albahari.com/threading / comme référence).

Si vous utilisez .NET 4.0, un parallèle pour est l'approche la plus simple.

0
Danny Varod

une méthode plus simple que j'aime utiliser:

    private int ThreadsCount = 100; //initialize threads count
    private void button1_Click(object sender, EventArgs e)
    {   
        for (int i = 0; i < ThreadsCount; i++)
        {
            Thread t = new Thread(new ThreadStart(myMethod));
            t.IsBackground = true;
            t.Start(); 
        } 
    }

    private void myMethod()
    {
        //everytime a thread finishes executing decrease the threads count
        ThreadsCount = ThreadsCount - 1;

        if (ThreadsCount < 1)
        {
            //if all threads finished executing do whatever you wanna do here..
            MessageBox.Show("Finished Executing all threads!!!");
        }
    }
0
SolidSnake

Publier pour aider peut-être d'autres personnes, a passé pas mal de temps à chercher une solution comme celle que j'ai trouvée. J'ai donc adopté une approche un peu différente. Je filais de nombreux threads et incrémentais un compteur et décrémentais un compteur pendant qu'un thread commençait et s'arrêtait. Ensuite, dans la méthode principale, je voulais faire une pause et attendre que les threads se terminent.

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

Documenté sur mon blog. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

0
Adam