web-dev-qa-db-fra.com

Comment attendre que le fil se termine avec .NET?

Je n'avais jamais vraiment utilisé de threading auparavant en C # où je devais avoir deux threads, ainsi que le thread d'interface utilisateur principal. Fondamentalement, j'ai ce qui suit.

public void StartTheActions()
{
  //Starting thread 1....
  Thread t1 = new Thread(new ThreadStart(action1));
  t1.Start();

  // Now, I want for the main thread (which is calling `StartTheActions` method) 
  // to wait for `t1` to finish. I've created an event in `action1` for this. 
  // The I wish `t2` to start...

  Thread t2 = new Thread(new ThreadStart(action2));
  t2.Start();
}

Donc, essentiellement, ma question est de savoir comment faire en sorte qu'un fil en attende un autre pour se terminer. Quelle est la meilleure façon de procéder?

161
Maxim Zaslavsky

Je peux voir 5 options disponibles:

1. Thread.Join

Comme avec la réponse de Mitch. Mais cela bloquera votre thread d'interface utilisateur, mais vous obtenez un délai d'expiration intégré pour vous.


2. Utilisez un WaitHandle

ManualResetEvent est un WaitHandle comme suggéré par Jrista.

Une chose à noter est que si vous voulez attendre plusieurs threads, WaitHandle.WaitAll() ne fonctionnera pas par défaut, car il nécessite un thread MTA. Vous pouvez contourner ce problème en marquant votre méthode Main() avec MTAThread. Toutefois, cela bloque la pompe à messages et n'est pas recommandé d'après ce que j'ai lu.


3. Organisez un événement

Voir cette page de Jon Skeet à propos des événements et du multi-threading, il est possible qu'un événement ne devienne pas abonné entre le if et le EventName(this,EventArgs.Empty) - c'est déjà arrivé auparavant.

(si tout va bien, je n'ai pas essayé de les compiler)

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();
        worker.ThreadDone += HandleThreadDone;

        Thread thread1 = new Thread(worker.Run);
        thread1.Start();

        _count = 1;
    }

    void HandleThreadDone(object sender, EventArgs e)
    {
        // You should get the idea this is just an example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();
            worker.ThreadDone += HandleThreadDone;

            Thread thread2 = new Thread(worker.Run);
            thread2.Start();

            _count++;
        }
    }

    class ThreadWorker
    {
        public event EventHandler ThreadDone;

        public void Run()
        {
            // Do a task

            if (ThreadDone != null)
                ThreadDone(this, EventArgs.Empty);
        }
    }
}

4. Utilisez un délégué

public class Form1 : Form
{
    int _count;

    void ButtonClick(object sender, EventArgs e)
    {
        ThreadWorker worker = new ThreadWorker();

        Thread thread1 = new Thread(worker.Run);
        thread1.Start(HandleThreadDone);

        _count = 1;
    }

    void HandleThreadDone()
    {
        // As before - just a simple example
        if (_count == 1)
        {
            ThreadWorker worker = new ThreadWorker();

            Thread thread2 = new Thread(worker.Run);
            thread2.Start(HandleThreadDone);

            _count++;
        }
    }

    class ThreadWorker
    {
        // Switch to your favourite Action<T> or Func<T>
        public void Run(object state)
        {
            // Do a task

            Action completeAction = (Action)state;
            completeAction.Invoke();
        }
    }
}

Si vous utilisez la méthode _count, cela pourrait être une idée (par sécurité) de l’incrémenter en utilisant

Interlocked.Increment(ref _count)

Je serais intéressé de connaître la différence entre l'utilisation de délégués et d'événements pour la notification de thread, la seule différence que je connaisse sont que les événements sont appelés de manière synchrone.


5. Faites-le plutôt asynchrone

La réponse à cette question décrit très clairement vos options avec cette méthode.


Déléguer/Événements sur le mauvais fil

La manière événementielle/déléguée de faire signifiera que votre méthode de gestionnaire d'événement est sur thread1/thread2 et non dans l'interface utilisateur principale. thread , il vous faudra donc revenir au début des méthodes HandleThreadDone:

// Delegate example
if (InvokeRequired)
{
    Invoke(new Action(HandleThreadDone));
    return;
}
256
Chris S

Ajouter

t1.Join();    // Wait until thread t1 finishes

après que vous l’ayez démarré, mais cela ne fera pas grand chose car c’est le même résultat que de courir sur le thread principal!

Je peux fortement recommander la lecture du livre électronique gratuit de Joe Albahari Threading in C # , si vous voulez comprendre le threading dans .NET.

55
Mitch Wheat

Les deux réponses précédentes sont excellentes et fonctionneront pour des scénarios simples. Il existe cependant d'autres moyens de synchroniser les threads. Ce qui suit fonctionnera également:

public void StartTheActions()
{
    ManualResetEvent syncEvent = new ManualResetEvent(false);

    Thread t1 = new Thread(
        () =>
        {
            // Do some work...
            syncEvent.Set();
        }
    );
    t1.Start();

    Thread t2 = new Thread(
        () =>
        {
            syncEvent.WaitOne();

            // Do some work...
        }
    );
    t2.Start();
}

ManualResetEvent est l'un des différents WaitHandle proposés par le framework .NET. Ils peuvent fournir des fonctionnalités de synchronisation de threads bien plus riches que les outils simples mais très courants tels que lock ()/Monitor, Thread.Join, etc. Ils peuvent également être utilisés pour synchroniser plus de deux threads, permettant ainsi des scénarios complexes tels qu'un thread "maître". qui coordonne plusieurs threads 'enfants', plusieurs processus simultanés dépendants de la synchronisation de plusieurs étapes, etc.

30
jrista

Si vous utilisez depuis .NET 4, cet exemple peut vous aider:

class Program
{
    static void Main(string[] args)
    {
        Task task1 = Task.Factory.StartNew(() => doStuff());
        Task task2 = Task.Factory.StartNew(() => doStuff());
        Task task3 = Task.Factory.StartNew(() => doStuff());
        Task.WaitAll(task1, task2, task3);
        Console.WriteLine("All threads complete");
    }

    static void doStuff()
    {
        //do stuff here
    }
}

à partir de: https://stackoverflow.com/a/4190969/1676736

22

Vous voulez la méthode Thread.Join() , ou l'une de ses surcharges .

5
Daniel Pryden

J'aurais votre thread principal passer une méthode de rappel à votre premier thread, et quand c'est fait, il invoquera la méthode de rappel sur le thread principal, qui peut lancer le second thread. Cela empêche votre fil principal de rester en attente pendant qu'il attend une jointure ou une attente. Passer des méthodes en tant que délégués est une chose utile à apprendre avec C # de toute façon.

4
Ed Power

Publier pour peut-être aider d'autres personnes a passé pas mal de temps à chercher une solution semblable à celle que j'ai proposée. J'ai donc adopté une approche légèrement différente. Il y a une option de compteur ci-dessus, je viens de l'appliquer un peu différemment. Je filais de nombreux threads et incrémentais un compteur et décrémentais un compteur lorsqu'un thread commençait et s'arrêtait. Ensuite, dans la méthode principale, je voulais faire une pause et attendre que les threads soient terminés.

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

Lorsque je souhaite que l'interface utilisateur puisse mettre à jour son affichage en attendant la fin d'une tâche, j'utilise une boucle while qui teste IsAlive sur le thread:

    Thread t = new Thread(() => someMethod(parameters));
    t.Start();
    while (t.IsAlive)
    {
        Thread.Sleep(500);
        Application.DoEvents();
    }

0
Mark Emerson