Come attendere il completamento del thread con .NET?


178

Non ho mai usato il threading prima in C # dove ho bisogno di avere due thread, così come il thread dell'interfaccia utente principale. Fondamentalmente, ho il seguente.

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

Quindi, in sostanza, la mia domanda è come avere un thread in attesa che ne finisca un altro. Qual è il modo migliore per farlo?


4
Se hai "... mai usato il threading prima ..." potresti prendere in considerazione alcune alternative più semplici come il modello * Async ().
Ðаn

4
Se stai solo aspettando che il thread 1 finisca comunque, perché non stai semplicemente chiamando quel metodo in modo sincrono?
Svish,

13
Che senso ha usare i thread quando si elabora in modo lineare?
Giovanni,

1
@John, ha perfettamente senso per me che ci sono molti usi per girare un thread in background che funziona mentre l'utente lavora. Inoltre, la tua domanda non è la stessa della precedente?
user34660,

La risposta di Rotem , usando il backgroundworker per un facile utilizzo, è molto semplice.
Kamil,

Risposte:


264

Posso vedere 5 opzioni disponibili:

1. Thread.Join

Come con la risposta di Mitch. Ma questo bloccherà il tuo thread dell'interfaccia utente, tuttavia ottieni un Timeout integrato per te.


2. Utilizzare a WaitHandle

ManualResetEventè un WaitHandlejrista come suggerito.

Una cosa da notare è se vuoi aspettare più thread, WaitHandle.WaitAll()non funzionerà di default, poiché ha bisogno di un thread MTA. Puoi aggirare questo contrassegnando il tuo Main()metodo con MTAThread- tuttavia questo blocca il tuo pump dei messaggi e non è raccomandato da quello che ho letto.


3. Attiva un evento

Vedi questa pagina di Jon Skeet su eventi e multi-threading, è possibile che un evento possa essere cancellato tra il ife il EventName(this,EventArgs.Empty)- è successo a me prima.

(Spero che questi compilano, non ho provato)

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. Utilizzare un delegato

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

Se si utilizza il metodo _count, potrebbe essere un'idea (per sicurezza) incrementarlo utilizzando

Interlocked.Increment(ref _count)

Sarei interessato a conoscere la differenza tra l'utilizzo di delegati ed eventi per la notifica di thread, l'unica differenza che conosco sono gli eventi chiamati in modo sincrono.


5. Fallo invece in modo asincrono

La risposta a questa domanda ha una descrizione molto chiara delle tue opzioni con questo metodo.


Delegato / Eventi sul thread errato

Il modo evento / delegato di fare le cose significherà che il tuo metodo del gestore eventi è su thread1 / thread2 non sul thread dell'interfaccia utente principale , quindi dovrai tornare indietro nella parte superiore dei metodi HandleThreadDone:

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

61

Inserisci

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

dopo averlo avviato, ma questo non compirà molto in quanto è essenzialmente lo stesso risultato della corsa sul thread principale!

Consiglio vivamente di leggere il threading di Joe Albahari nell'e-book gratuito di C # , se vuoi avere una comprensione del threading in .NET.


4
Anche se Joinè letteralmente ciò che sembra chiedere al richiedente, questo può essere estremamente negativo in generale. Una chiamata a Joinriaggancerà il thread da cui questo viene fatto. Se questo sembra essere il thread principale della GUI, questo è MALE ! Come utente detesto attivamente le applicazioni che sembrano funzionare in questo modo. Quindi, per favore vedere tutte le altre risposte a questa domanda e stackoverflow.com/questions/1221374/...
peSHIr

1
Sono d'accordo che in generale Join () è male. Forse non ho reso abbastanza ovvio nella mia risposta.
Mitch Wheat,

2
Ragazzi, una taglia non va bene per tutti . Ci sono situazioni in cui bisogna veramente essere sicuri che il thread abbia terminato il suo lavoro: considerare che il thread elabora i dati che stanno per essere modificati. In tal caso, la notifica dell'IME di annullare il thread con grazia e attendere fino al termine (in particolare, quando un passaggio viene elaborato molto rapidamente) è pienamente giustificata dall'IMO. Preferirei dire che Join è malvagio (in termini di FAQ C ++), vale a dire. non deve essere usato a meno che non sia veramente necessario.
Spook

5
Voglio affermare chiaramente che Join è uno strumento che può essere utile, nonostante sia molto spesso usato in modo improprio. Ci sono alcune situazioni in cui funzionerà senza effetti collaterali inutili (come bloccare il thread della GUI principale per un periodo di tempo notevole).
Spook

2
Iscriviti è uno strumento? Penso che troverai che è un metodo.
Mitch Wheat,

32

Le due risposte precedenti sono fantastiche e funzioneranno per scenari semplici. Esistono altri modi per sincronizzare i thread, tuttavia. Funzionerà anche:

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 è uno dei vari WaitHandle che .NET Framework ha da offrire. Possono fornire funzionalità di sincronizzazione dei thread molto più ricche rispetto agli strumenti semplici ma molto comuni come lock () / Monitor, Thread.Join, ecc. Possono anche essere utilizzati per sincronizzare più di due thread, consentendo scenari complessi come un thread "master" che coordina più thread "figlio", più processi simultanei che dipendono da più fasi l'uno dall'altro per la sincronizzazione, ecc.


30

Se si utilizza da .NET 4 questo esempio può aiutarti:

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

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


8
Non hai menzionato nulla delle discussioni nella tua risposta. La domanda riguarda le discussioni, non le attività. I due non sono la stessa cosa.
Suamere,

7
Sono arrivato con una domanda simile (e livello di conoscenza) al poster originale e questa risposta è stata molto preziosa per me - le attività sono molto più adatte a quello che sto facendo e se non avessi trovato questa risposta avrei scritto il mio proprio terribile pool di thread.
Chris Rae,

1
@ChrisRae, quindi questo dovrebbe essere un commento nella domanda originale, non una risposta come questa.
Jaime Hablutzel,

Poiché si tratta di questa risposta specifica, penso che probabilmente abbia più senso qui.
Chris Rae,

1
come ha detto @Suamere, questa risposta non è assolutamente correlata alla domanda del PO.
Glenn Slayden,


4

Vorrei che il tuo thread principale passasse un metodo di callback al tuo primo thread e, al termine, invocherà il metodo di callback sul mainthread, che può avviare il secondo thread. Ciò evita che il thread principale si blocchi mentre è in attesa di Join o Waithandle. Passare metodi come delegati è comunque utile imparare con C #.


0

Prova questo:

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

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

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

0

Pubblicare messaggi per aiutare alcuni altri, ha trascorso un bel po 'di tempo a cercare una soluzione come quella che mi è venuta in mente. Quindi ho adottato un approccio leggermente diverso. C'è un'opzione contatore sopra, l'ho appena applicata in modo leggermente diverso. Stavo filando numerosi thread, incrementando un contatore e decrementando un contatore all'avvio e all'arresto di un thread. Quindi, nel metodo principale, volevo mettere in pausa e attendere il completamento dei thread.

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

Documentato sul mio blog. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/


4
Questo si chiama attesa di occupato. Sì, funziona e talvolta è la soluzione migliore, ma se possibile
evitalo,

@MobileMon è più una linea guida che una regola. In questo caso, dato che quel loop potrebbe perdere lo 0,00000001% della CPU e l'OP sta codificando in C #, sostituirlo con qualcosa di "più efficiente" sarebbe una completa perdita di tempo. La prima regola di ottimizzazione è: non farlo. Misura prima.
Spike0xff,

0

Quando voglio che l'interfaccia utente sia in grado di aggiornare il suo display mentre aspetto che un'attività venga completata, uso un ciclo while che testa IsAlive sul thread:

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

-1

Ecco un semplice esempio che attende che un battistrada finisca, all'interno della stessa classe. Inoltre effettua una chiamata a un'altra classe nello stesso spazio dei nomi. Ho incluso le istruzioni "using" in modo che possa essere eseguito come Winform fino a quando si crea 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}";
  }
 }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.