Jak mogę czekać na zakończenie wątku with.NET?

Nigdy wcześniej nie używałem threadingu w C#, gdzie muszę mieć dwa wątki, a także główny wątek interfejsu użytkownika. Zasadniczo, mam następujące.

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

Więc, zasadniczo, jak Mogę mieć wątek czekać na inny, aby zakończyć? Jak najlepiej to zrobić?

Author: Peter Mortensen, 2009-10-18

10 answers

Widzę pięć dostępnych opcji:

1. Nić.Join

Jak z odpowiedzią Mitcha. Ale to zablokuje Twój wątek UI, jednak otrzymasz limit czasu Wbudowany dla Ciebie.


2. Użyj WaitHandle

ManualResetEvent jest WaitHandle Jak zasugerował jrista.

Należy pamiętać, że jeśli chcesz czekać na wiele wątków: WaitHandle.WaitAll()nie będzie działać domyślnie, ponieważ potrzebuje MTA. Możesz to obejść, zaznaczając swoją metodę Main() za pomocą MTAThread - jednak blokuje to twoja wiadomość i nie jest polecana z tego co czytałem.


3. Fire an event

Zobacz tę stronę Jona Skeeta o wydarzeniach i wielowątkowości. Możliwe, że między if a EventName(this,EventArgs.Empty) zdarzenie może zostać anulowane - zdarzyło mi się to już wcześniej.

(mam nadzieję, że te kompilacji, nie próbowałem)

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. Użyj delegata

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

Jeśli używasz metody _count, może być to pomysł (aby być bezpiecznym), aby increment it using

Interlocked.Increment(ref _count)

Byłbym zainteresowany poznaniem różnicy między używaniem delegatów i zdarzeń do powiadamiania wątków, jedyną różnicą, jaką znam, jest to, że zdarzenia są nazywane synchronicznie.


5. Zrób to asynchronicznie zamiast

Odpowiedź na to pytanie zawiera bardzo jasny opis twoich opcji z tą metodą.


Delegat / wydarzenia w złym wątku

Sposób działania zdarzenia/delegata będzie oznaczał twoja metoda obsługi zdarzeń znajduje się w wątku thread1/thread2 , a nie głównym wątku interfejsu, więc musisz przełączyć się z powrotem na górę metod HandleThreadDone:

// Delegate example
if (InvokeRequired)
{
    Invoke(new Action(HandleThreadDone));
    return;
}
 270
Author: Chris S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-14 01:27:19

Dodaj

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

Po uruchomieniu go, ale to nie przyniesie wiele, ponieważ jest to zasadniczo taki sam wynik jak uruchomienie na głównym wątku!

Jeśli chcesz dowiedzieć się więcej o threadingu w domenie .NET, to polecam lekturę Joego Albahariego Threading w C# darmowego e-booka, jeśli chcesz zrozumieć threading w domenie. NET.
 66
Author: Mitch Wheat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-18 05:09:42

Poprzednie dwie odpowiedzi są świetne i sprawdzą się w prostych scenariuszach. Istnieją jednak inne sposoby synchronizacji wątków. Będą również działać:

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 jest jednym z różnych WaitHandle ' S, że. NET framework ma do zaoferowania. Mogą one zapewnić znacznie bogatsze możliwości synchronizacji wątków niż proste, ale bardzo popularne narzędzia, takie jak lock () / Monitor, Thread.Dołącz, itp.

Mogą być również używane do synchronizacji więcej niż dwóch threads, umożliwiając złożone scenariusze, takie jak wątek "master", który koordynuje wiele wątków "potomnych", wiele równoległych procesów, które są zależne od kilku etapów siebie do synchronizacji, itp.

 34
Author: jrista,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-14 01:28:18

Jeśli używasz Z. NET 4 ta próbka może Ci pomóc:

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

Od: Utwórz wiele wątków i poczekaj na ich zakończenie

 33
Author: Sayed Abolfazl Fatemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-14 01:35:00

Chcesz Thread.Join() Metoda, lub jedno z jej przeciążeń .

 5
Author: Daniel Pryden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-18 05:12:55

Chciałbym, aby twój główny wątek przekazał metodę callback do pierwszego wątku, a kiedy to się skończy, wywoła metodę callback na wątku głównym, który może uruchomić drugi wątek. Dzięki temu główny wątek nie zawiesi się podczas oczekiwania na połączenie lub czekanie. Przekazywanie metod jako delegatów jest przydatną rzeczą do nauki w C# i tak.

 4
Author: Ed Power,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-22 22:15:20

Spróbuj tego:

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

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

foreach (Thread curThread in myThreads)
{
    curThread.Join();
}
 0
Author: Wendell Tagsip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-14 17:16:41

Kiedy chcę, aby interfejs użytkownika był w stanie zaktualizować swój wyświetlacz czekając na ukończenie zadania, używam pętli while, która testuje IsAlive w wątku:

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

 0
Author: Mark Emerson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-22 19:18:32

Przyjęłam nieco inne podejście. W poprzednich odpowiedziach jest opcja licznika i zastosowałem ją nieco inaczej. Obracałem wiele wątków i zwiększałem licznik i zmniejszałem licznik, gdy wątek zaczynał się i zatrzymywał. Następnie w głównej metodzie chciałem pauzować i czekać na zakończenie wątków zrobiłem.

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

Jest to udokumentowane w moim wpisie na blogu: http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

 0
Author: Adam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-14 01:31:08

Oto prosty przykład, który czeka na zakończenie bieżnika w tej samej klasie. Wykonuje również wywołanie innej klasy w tej samej przestrzeni nazw. Dodałem instrukcje "using", aby można było wykonać jako Formularz Windows Forms tak długo, jak utworzysz button1.

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}";
        }
    }
}
 -1
Author: user3029478,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-14 01:38:06