Como esperar que o thread termine com o .NET?


178

Eu realmente nunca usei threading antes em C #, onde preciso ter dois threads, bem como o thread principal da interface do usuário. Basicamente, eu tenho o seguinte.

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

Então, essencialmente, minha pergunta é como fazer com que um thread aguarde o término de outro. Qual é a melhor maneira de fazer isso?


4
Se você "... nunca usou o encadeamento antes ...", convém considerar algumas alternativas mais simples, como o padrão * Async ().
18/10/2009

4
Se você está apenas aguardando o término do thread 1, por que não está chamando esse método de forma síncrona?
Svish

13
Qual é o sentido de usar threads ao processar de maneira linear?
John John

1
@ John, faz total sentido para mim que existem muitos usos para criar um thread em segundo plano que funcione enquanto o usuário trabalha. Além disso, sua pergunta não é a mesma que a anterior?
precisa saber é o seguinte

A resposta do Rotem , usando o backgroundworker para facilitar o uso, é muito simples.
21918 Kamil

Respostas:


264

Eu posso ver 5 opções disponíveis:

1. Thread.Join

Como na resposta de Mitch. Mas isso bloqueará o encadeamento da interface do usuário, no entanto, você obtém um tempo limite incorporado para você.


2. Use um WaitHandle

ManualResetEventé um WaitHandlecomo jrista sugeriu.

Uma coisa a ser observada é que, se você deseja esperar por vários threads, WaitHandle.WaitAll()não funcionará por padrão, pois precisa de um thread MTA. Você pode contornar isso marcando seu Main()método com MTAThread- no entanto, isso bloqueia sua bomba de mensagens e não é recomendado pelo que li.


3. Dispare um evento

Consulte esta página de Jon Skeet sobre eventos e multiencadeamento, é possível que um evento possa ser cancelado de inscrição entre o ife oEventName(this,EventArgs.Empty) - já aconteceu comigo antes.

(Espero que estes compilem, eu não tentei)

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. Use um delegado

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 você usar o método _count, pode ser uma ideia (para ser seguro) incrementá-lo usando

Interlocked.Increment(ref _count)

Eu estaria interessado em saber a diferença entre o uso de delegados e eventos para notificação de threads, a única diferença que eu sei são os eventos que são chamados de forma síncrona.


5. Faça de forma assíncrona

A resposta a esta pergunta tem uma descrição muito clara de suas opções com este método.


Delegar / Eventos no segmento errado

A maneira de fazer o evento / delegar significa que o método do manipulador de eventos está no thread1 / thread2 e não no thread da interface principal da interface do usuário ; portanto, você precisará voltar à parte superior dos métodos HandleThreadDone:

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

61

Adicionar

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

depois de iniciá-lo, mas isso não resultará muito, pois é essencialmente o mesmo resultado que a execução no thread principal!

Eu recomendo a leitura de e-book gratuito Threading em C #, de Joe Albahari , se você quiser obter uma compreensão do threading no .NET.


4
Mesmo que Joinseja literalmente o que o solicitante parece estar pedindo, isso pode ser extremamente ruim em geral. Uma chamada para Joindesligará o encadeamento a partir do qual isso está sendo feito. Se esse for o principal thread da GUI, isso é RUIM ! Como usuário, detesto ativamente aplicativos que parecem funcionar dessa maneira. Portanto, consulte todas as outras respostas a esta pergunta e stackoverflow.com/questions/1221374/…
peSHIr

1
Concordo que, em geral, Join () é ruim. Talvez eu não tenha deixado isso óbvio o suficiente na minha resposta.
Mitch Wheat

2
Gente, um tamanho não serve para todos . Há situações em que é preciso ter certeza de que o encadeamento terminou seu trabalho: considere que o encadeamento processa os dados, que estão prestes a serem alterados. Nesse caso, notificar o encadeamento para cancelar normalmente e aguardar o término (especialmente quando uma etapa é processada muito rapidamente) é totalmente justificado pela IMO. Eu prefiro dizer que Join é mau (em termos de C ++ FAQ), ie. não deve ser utilizado, a menos que seja realmente necessário.
Spook

5
Quero afirmar claramente que o Join é uma ferramenta que pode ser útil, apesar do fato, que é frequentemente mal utilizada. Existem algumas situações em que funcionará sem efeitos colaterais desnecessários (como travar o encadeamento principal da GUI por um período notável de tempo).
Spook

2
Join é uma ferramenta? Eu acho que você achará que é um método.
Mitch Wheat

32

As duas respostas anteriores são ótimas e funcionarão para cenários simples. Existem outras maneiras de sincronizar threads, no entanto. O seguinte também funcionará:

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 é um dos vários WaitHandle que a estrutura .NET tem a oferecer. Eles podem fornecer recursos de sincronização de encadeamento muito mais avançados do que as ferramentas simples, mas muito comuns, como lock () / Monitor, Thread.Join etc. Eles também podem ser usados ​​para sincronizar mais de dois encadeamentos, permitindo cenários complexos, como um encadeamento 'mestre' que coordena vários segmentos 'filhos', vários processos simultâneos que dependem de vários estágios um do outro para serem sincronizados etc.


30

Se estiver usando do .NET 4, este exemplo pode ajudá-lo:

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

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


8
Você não mencionou nada sobre os Threads na sua resposta. A pergunta é sobre threads, não tarefas. Os dois não são o mesmo.
Suamere

7
Eu vim com uma pergunta semelhante (e nível de conhecimento) ao pôster original e essa resposta foi muito valiosa para mim - as tarefas são muito mais adequadas para o que estou fazendo e, se eu não tivesse encontrado essa resposta, teria escrito o meu próprio pool de threads terrível.
22418 Chris Rae

1
@ ChrisRae, então isso deve ser um comentário na pergunta original, não uma resposta como esta.
Jaime Hablutzel

Como se trata dessa resposta específica, acho que provavelmente faz mais sentido aqui.
Chris Rae

1
como o @Suamere disse, esta resposta não tem nenhuma relação com a pergunta do OP.
Glenn Slayden


4

Gostaria que seu thread principal passasse um método de retorno de chamada para o seu primeiro thread e, quando terminado, invocaria o método de retorno de chamada no mainthread, que pode iniciar o segundo thread. Isso evita que o encadeamento principal fique travado enquanto aguarda um Join ou Waithandle. Passar métodos como delegados é uma coisa útil para aprender com C # de qualquer maneira.


0

Tente o seguinte:

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

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

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

0

Postar para talvez ajudar outras pessoas, gastou bastante tempo procurando uma solução como a que eu vim. Então, adotei uma abordagem um pouco diferente. Existe uma opção de contador acima, apenas a apliquei de maneira um pouco diferente. Eu estava gerando vários threads e incrementando um contador e diminuindo o contador quando um thread começou e parou. Então, no método principal, eu queria pausar e aguardar a conclusão dos threads.

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

Documentado no meu blog. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/


4
Isso é chamado de espera ocupada. Sim ele funciona e às vezes é a melhor solução, mas você quer evitá-lo se possível porque desperdiça tempo CPU
MobileMon

@MobileMon é mais uma diretriz do que uma regra. Nesse caso, como esse loop pode desperdiçar 0,00000001% da CPU e o OP está codificando em C #, substituí-lo por algo 'mais eficiente' seria uma completa perda de tempo. A primeira regra de otimização é - não. Meça primeiro.
Spike0xff

0

Quando quero que a interface do usuário consiga atualizar sua exibição enquanto aguarda a conclusão de uma tarefa, uso um loop while que testa IsAlive no thread:

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

-1

Aqui está um exemplo simples que aguarda a conclusão de uma banda de rodagem, dentro da mesma classe. Também faz uma chamada para outra classe no mesmo espaço para nome. Incluí as instruções "using" para que ele possa ser executado como um Winform, desde que você crie o 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}";
  }
 }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.