Wie kann ich warten, bis der Thread mit .NET beendet ist?

178

Ich habe noch nie zuvor Threading in C # verwendet, wo ich zwei Threads sowie den Haupt-UI-Thread haben muss. Grundsätzlich habe ich folgendes.

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();
}

Meine Frage ist also im Wesentlichen, wie man einen Thread warten lässt, bis ein anderer fertig ist. Was ist der beste Weg, dies zu tun?

Maxim Zaslavsky
quelle
4
Wenn Sie "... noch nie zuvor Threading verwendet haben ...", sollten Sie einige einfachere Alternativen in Betracht ziehen, z. B. das * Async () -Muster.
Am
4
Wenn Sie nur darauf warten, dass Thread 1 trotzdem beendet wird, warum rufen Sie diese Methode nicht einfach synchron auf?
Svish
13
Was bringt es, Threads zu verwenden, wenn Sie linear verarbeiten?
John
1
@ John, es macht für mich total Sinn, dass es viele Verwendungsmöglichkeiten gibt, um einen Hintergrund-Thread abzuspinnen, der funktioniert, während der Benutzer arbeitet. Ist Ihre Frage nicht dieselbe wie die vorherige?
user34660
Die Antwort von Rotem , bei der Hintergrundarbeit für eine einfache Verwendung verwendet wird, ist sehr einfach.
Kamil

Antworten:

263

Ich kann 5 verfügbare Optionen sehen:

1. Thread.Join

Wie bei Mitch's Antwort. Dies blockiert jedoch Ihren UI-Thread, Sie erhalten jedoch ein Timeout, das für Sie integriert ist.


2. Verwenden Sie a WaitHandle

ManualResetEventist ein WaitHandlewie jrista vorgeschlagen.

Wenn Sie auf mehrere Threads warten möchten, WaitHandle.WaitAll()funktioniert dies standardmäßig nicht, da ein MTA-Thread erforderlich ist. Sie können dies umgehen, indem Sie Ihre Main()Methode mit markieren. MTAThreadDies blockiert jedoch Ihre Nachrichtenpumpe und wird nach dem, was ich gelesen habe, nicht empfohlen.


3. Feuern Sie ein Ereignis ab

Auf dieser Seite von Jon Skeet finden Sie Informationen zu Ereignissen und Multithreading. Es ist möglich, dass ein Ereignis zwischen dem ifund dem nicht mehr abonniert wirdEventName(this,EventArgs.Empty) - es ist mir schon einmal passiert.

(Hoffentlich kompilieren diese, ich habe es nicht versucht)

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. Verwenden Sie einen Delegaten

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();
        }
    }
}

Wenn Sie die Methode _count verwenden, ist es möglicherweise (aus Sicherheitsgründen) eine Idee, sie mit zu erhöhen

Interlocked.Increment(ref _count)

Mich würde interessieren, welchen Unterschied es zwischen der Verwendung von Delegaten und Ereignissen für die Thread-Benachrichtigung gibt. Der einzige Unterschied, den ich kenne, besteht darin, dass Ereignisse synchron aufgerufen werden.


5. Tun Sie es stattdessen asynchron

Die Antwort auf diese Frage enthält eine sehr klare Beschreibung Ihrer Optionen mit dieser Methode.


Delegate / Events im falschen Thread

Die Veranstaltung / Delegierten Art , die Dinge werden Ihre Event - Handler bedeuten Methode ist auf thread1 / thread2 nicht die Haupt - UI - Thread , so müssen Sie direkt an der Spitze der HandleThreadDone Methoden zurückschalten:

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

Hinzufügen

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

nachdem Sie es gestartet haben, aber das wird nicht viel bewirken, da es im Wesentlichen das gleiche Ergebnis ist wie das Ausführen auf dem Haupt-Thread!

Ich kann Joe Albaharis Threading in C # -freiem E-Book nur empfehlen , wenn Sie sich mit Threading in .NET vertraut machen möchten.

Mitch Wheat
quelle
4
Obwohl dies Joinbuchstäblich das ist, wonach der Fragesteller gefragt zu haben scheint, kann dies im Allgemeinen äußerst schlecht sein. Ein Aufruf von Joinlegt den Thread auf, von dem aus dies ausgeführt wird. Wenn das der Haupt-GUI-Thread ist, ist dies SCHLECHT ! Als Benutzer verabscheue ich aktiv Anwendungen, die so zu funktionieren scheinen. Also bitte alle anderen Antworten auf diese Frage und stackoverflow.com/questions/1221374/…
peSHIr
1
Ich bin damit einverstanden, dass Join () im Allgemeinen schlecht ist. Ich habe das in meiner Antwort vielleicht nicht deutlich genug gemacht.
Mitch Wheat
2
Leute, eine Größe passt nicht für alle . Es gibt Situationen, in denen man wirklich sicher sein muss, dass der Thread seine Arbeit beendet hat: Bedenken Sie, dass der Thread die Daten verarbeitet, die gerade geändert werden sollen. In diesem Fall ist es IMO völlig gerechtfertigt, den Thread zu benachrichtigen, dass er ordnungsgemäß abgebrochen werden soll, und zu warten, bis er beendet ist (insbesondere, wenn ein Schritt sehr schnell verarbeitet wird). Ich würde eher sagen, dass Join böse ist (in C ++ FAQ-Begriffen), dh. Es darf nur verwendet werden, wenn dies wirklich erforderlich ist.
Spook
5
Ich möchte klarstellen, dass Join ein Tool ist, das nützlich sein kann, obwohl es sehr oft missbraucht wird. Es gibt einige Situationen, in denen es nur ohne unnötige Nebenwirkungen funktioniert (z. B. das Blockieren des Haupt-GUI-Threads für einen spürbaren Zeitraum).
Spook
2
Join ist ein Werkzeug? Ich denke, Sie werden feststellen, dass es eine Methode ist.
Mitch Wheat
32

Die beiden vorherigen Antworten sind großartig und funktionieren für einfache Szenarien. Es gibt jedoch auch andere Möglichkeiten, Threads zu synchronisieren. Folgendes wird auch funktionieren:

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 ist eines der verschiedenen WaitHandles , die das .NET Framework bietet. Sie bieten viel umfangreichere Funktionen zur Thread-Synchronisierung als die einfachen, aber sehr verbreiteten Tools wie lock () / Monitor, Thread.Join usw. Sie können auch zum Synchronisieren von mehr als zwei Threads verwendet werden, wodurch komplexe Szenarien wie ein 'Master'-Thread ermöglicht werden das koordiniert mehrere "untergeordnete" Threads, mehrere gleichzeitige Prozesse, die von mehreren zu synchronisierenden Stufen voneinander abhängig sind, usw.

jrista
quelle
29

Bei Verwendung von .NET 4 kann dieses Beispiel Ihnen helfen:

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
    }
}

von: https://stackoverflow.com/a/4190969/1676736

Sagte Abolfazl Fatemi
quelle
8
Sie haben in Ihrer Antwort nichts über Threads erwähnt. Die Frage bezieht sich auf Threads, nicht auf Aufgaben. Die beiden sind nicht gleich.
Suamere
7
Ich kam mit einer ähnlichen Frage (und einem ähnlichen Wissensstand) wie das Originalplakat und diese Antwort war für mich sehr wertvoll - Aufgaben sind viel besser für meine Arbeit geeignet, und wenn ich diese Antwort nicht gefunden hätte, hätte ich meine geschrieben eigener schrecklicher Thread-Pool.
Chris Rae
1
@ChrisRae, also sollte dies ein Kommentar in der ursprünglichen Frage sein, keine Antwort wie diese.
Jaime Hablutzel
Da es um diese spezifische Antwort geht, denke ich, dass es hier wahrscheinlich sinnvoller ist.
Chris Rae
1
Wie @Suamere sagte, hat diese Antwort nichts mit der OP-Frage zu tun.
Glenn Slayden
4

Ich würde Ihren Hauptthread eine Rückrufmethode an Ihren ersten Thread übergeben lassen, und wenn dies erledigt ist, wird die Rückrufmethode auf dem Hauptthread aufgerufen, die den zweiten Thread starten kann. Dies verhindert, dass Ihr Hauptfaden hängt, während er auf einen Join oder Waithandle wartet. Das Übergeben von Methoden als Delegaten ist ohnehin eine nützliche Sache, um mit C # zu lernen.

Ed Power
quelle
0

Versuche dies:

List<Thread> myThreads = new List<Thread>();

foreach (Thread curThread in myThreads)
{
    curThread.Start();
}

foreach (Thread curThread in myThreads)
{
    curThread.Join();
}
Wendell Tagsip
quelle
0

Das Posten, um vielleicht anderen zu helfen, hat ziemlich viel Zeit damit verbracht, nach einer Lösung zu suchen, wie ich sie mir ausgedacht habe. Also habe ich einen etwas anderen Ansatz gewählt. Es gibt oben eine Zähleroption, ich habe sie nur etwas anders angewendet. Ich drehte zahlreiche Threads ab und erhöhte einen Zähler und dekrementierte einen Zähler, als ein Thread gestartet und gestoppt wurde. Dann wollte ich in der Hauptmethode pausieren und warten, bis die Threads abgeschlossen sind.

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

In meinem Blog dokumentiert. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

Adam
quelle
4
Dies wird als beschäftigtes Warten bezeichnet. Ja, es funktioniert und ist manchmal die beste Lösung, aber Sie möchten es nach Möglichkeit vermeiden, da es CPU-Zeit verschwendet
MobileMon
@MobileMon das ist eher eine Richtlinie als eine Regel. In diesem Fall wäre es reine Zeitverschwendung, diese Schleife durch C # zu ersetzen, da diese möglicherweise 0,00000001% der CPU verschwendet und das OP in C # codiert. Die erste Regel der Optimierung lautet - nicht. Zuerst messen.
Spike0xff
0

Wenn ich möchte, dass die Benutzeroberfläche ihre Anzeige aktualisieren kann, während sie auf den Abschluss einer Aufgabe wartet, verwende ich eine while-Schleife, die IsAlive für den Thread testet:

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

Hier ist ein einfaches Beispiel, das darauf wartet, dass ein Profil innerhalb derselben Klasse fertig ist. Es ruft auch eine andere Klasse im selben Namespace auf. Ich habe die "using" -Anweisungen eingefügt, damit sie als Winform ausgeführt werden können, solange Sie button1 erstellen.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace ClassCrossCall
{

 public partial class Form1 : Form
 {
  int number = 0; // this is an intentional problem, included for demonstration purposes
  public Form1()
  {
   InitializeComponent();
  }
  private void Form1_Load(object sender, EventArgs e)
  {
   button1.Text="Initialized";
  }
  private void button1_Click(object sender, EventArgs e)
  {
   button1.Text="Clicked";
   button1.Refresh();
   Thread.Sleep(400);
   List<Task> taskList = new List<Task>();
   taskList.Add(Task.Factory.StartNew(() => update_thread(2000)));
   taskList.Add(Task.Factory.StartNew(() => update_thread(4000)));
   Task.WaitAll(taskList.ToArray());
   worker.update_button(this,number);
  }
  public void update_thread(int ms)
  {
   // It's important to check the scope of all variables
   number=ms; // this could be either 2000 or 4000.  Race condition.
   Thread.Sleep(ms);
  }
 }

 class worker
 {
  public static void update_button(Form1 form, int number)
  {
   form.button1.Text=$"{number}";
  }
 }
}
user3029478
quelle