Как подождать, пока поток завершится с .NET?

Я никогда не использовал threading раньше в С#, где мне нужно иметь два потока, а также основной поток пользовательского интерфейса. В принципе, у меня есть следующее.

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

Итак, по сути, мой вопрос заключается в том, как заставить поток ждать, пока другой закончит. Каков наилучший способ сделать это?

Ответ 1

Я вижу 5 доступных вариантов:

1. Thread.join

Как и в случае с Митчем. Но это заблокирует ваш поток пользовательского интерфейса, однако вы получите встроенный тайм-аут.


2. Используйте WaitHandle

ManualResetEvent - это WaitHandle, как предлагалось jrista.

Следует отметить, что если вы хотите подождать несколько потоков, WaitHandle.WaitAll() не будет работать по умолчанию, так как ему нужен поток MTA. Вы можете обойти это, пометив ваш метод Main() MTAThread - однако это блокирует ваш насос сообщений и не рекомендуется из того, что я читал.


3. Пожар события

См. эту страницу Jon Skeet о событиях и многопоточности, возможно, что событие может стать неподписанным между if и EventName(this,EventArgs.Empty) - это случилось со мной раньше.

(Надеюсь, эти компиляции я не пробовал)

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. Использовать делегат

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

Если вы используете метод _count, может быть идея (чтобы быть в безопасности), чтобы увеличить его, используя

Interlocked.Increment(ref _count)

Мне было бы интересно узнать разницу между использованием делегатов и событий для уведомления о потоке, единственная разница, которую я знаю, - это события, которые вызывают синхронно.


5. Сделайте это асинхронно вместо

Ответ на этот вопрос содержит очень четкое описание ваших вариантов с помощью этого метода.


Делегировать/события в неправильном потоке

Событие/делегировать способ выполнения действий будет означать, что ваш обработчик событий находится в thread1/thread2 не в главном потоке пользовательского интерфейса, поэтому вам нужно будет переключиться обратно вправо в верхней части методов HandleThreadDone:

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

Ответ 2

Добавить

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

после того, как вы запустите его, но это не будет достигнуто, поскольку это очень важно, как работает в основном потоке!

Я очень рекомендую прочитать бесплатную электронную книгу Joe Albahari Threading in С#, если вы хотите получить представление о потоковой передаче в .NET.

Ответ 3

Предыдущие два ответа велики и будут работать для простых сценариев. Однако есть и другие способы синхронизации потоков. Также будет работать следующее:

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 является одним из различных WaitHandle, который может предложить платформа .NET. Они могут обеспечить гораздо более богатые возможности синхронизации потоков, чем простые, но очень распространенные инструменты, такие как lock()/Monitor, Thread.Join и т.д. Они также могут использоваться для синхронизации более двух потоков, что позволяет использовать сложные сценарии, такие как "главный" поток который координирует множественные "дочерние" потоки, несколько параллельных процессов, которые зависят от синхронизации нескольких этапов друг от друга и т.д.

Ответ 4

Если вы используете .NET 4, этот пример поможет вам:

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: fooobar.com/questions/45890/...

Ответ 6

Я хочу, чтобы ваш основной поток передал метод обратного вызова в ваш первый поток, и когда он будет выполнен, он вызовет метод обратного вызова на mainthread, который может запустить второй поток. Это удерживает ваш основной поток от зависания, пока он ждет Join или Waithandle. Передача методов в качестве делегатов - это полезная вещь, чтобы учиться на С# в любом случае.

Ответ 7

Проводя, возможно, помогая некоторым другим, потратил немало времени на поиск решения, как то, что я придумал. Поэтому я принял несколько иной подход. Существует опция счетчика выше, я просто применил ее немного по-другому. Я откручивал множество потоков и увеличивал счетчик и уменьшал счетчик, когда поток начинался и останавливался. Затем в основном методе, который я хотел сделать паузой и ждать завершения потоков, я сделал.

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

Документировано в моем блоге. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

Ответ 8

Когда я хочу, чтобы пользовательский интерфейс мог обновлять отображение во время ожидания завершения задачи, я использую цикл while, который проверяет IsAlive в потоке:

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

Ответ 9

Попробуйте следующее:

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

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

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