Создайте несколько потоков и дождитесь их завершения.

Как создать несколько потоков и дождаться их завершения?

Ответ 1

Это зависит от того, какую версию .NET Framework вы используете..NET 4.0 упрощает управление потоками, используя Задачи:

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

В предыдущих версиях .NET вы могли использовать объект BackgroundWorker, использовать ThreadPool.QueueUserWorkItem() или создать свои потоки вручную и использовать Thread.Join(), чтобы дождаться их завершения:

static void Main(string[] args)
{
    Thread t1 = new Thread(doStuff);
    t1.Start();

    Thread t2 = new Thread(doStuff);
    t2.Start();

    Thread t3 = new Thread(doStuff);
    t3.Start();

    t1.Join();
    t2.Join();
    t3.Join();

    Console.WriteLine("All threads complete");
}

Ответ 2

Думаю, вам нужно WaitHandler.WaitAll. Вот пример:

public static void Main(string[] args)
{
    int numOfThreads = 10;
    WaitHandle[] waitHandles = new WaitHandle[numOfThreads];

    for (int i = 0; i < numOfThreads; i++)
    {
        var j = i;
        // Or you can use AutoResetEvent/ManualResetEvent
        var handle = new EventWaitHandle(false, EventResetMode.ManualReset);
        var thread = new Thread(() =>
                                {
                                    Thread.Sleep(j * 1000);
                                    Console.WriteLine("Thread{0} exits", j);
                                    handle.Set();
                                });
        waitHandles[j] = handle;
        thread.Start();
    }
    WaitHandle.WaitAll(waitHandles);
    Console.WriteLine("Main thread exits");
    Console.Read();
}

EDIT У FCL есть еще несколько удобных функций.

(1) Task.WaitAll, а также его перегрузки, когда вы хотите выполнять некоторые задачи параллельно (и без возвращаемых значений).

var tasks = new[]
{
    Task.Factory.StartNew(() => DoSomething1()),
    Task.Factory.StartNew(() => DoSomething2()),
    Task.Factory.StartNew(() => DoSomething3())
};    
Task.WaitAll(tasks);

(2) Task.WhenAll, когда вы хотите выполнить некоторые задачи с возвращаемыми значениями, он выполняет операции и помещает результаты в массив, Это поточно-безопасный, вам не нужно использовать поточно-безопасный контейнер и реализовать операцию добавления самостоятельно.

var tasks = new[]
{
    Task.Factory.StartNew(() => GetSomething1()),
    Task.Factory.StartNew(() => GetSomething2()),
    Task.Factory.StartNew(() => GetSomething3())
};    
var things = Task.WhenAll(tasks);

Ответ 3

В .Net 4.0 вы можете использовать параллельную библиотеку задач.

В более ранних версиях вы можете создать список объектов Thread в цикле, вызвав Start на каждом из них, затем сделать еще один цикл и вызвать Join на каждом из них.

Ответ 4

Я сделал очень простой метод расширения для ожидания всех потоков коллекции:

using System.Collections.Generic;
using System.Threading;
namespace Extensions
{
    public static class ThreadExtension
    {
        public static void WaitAll(this IEnumerable<Thread> threads)
        {
            if(threads!=null)
            {
                foreach(Thread thread in threads)
                { thread.Join(); }
            }
        }
    }
}

Затем вы просто вызываете:

List<Thread> threads=new List<Thread>();
//Add your threads to this collection
threads.WaitAll();

Ответ 5

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

private object _lock=new object();
private int _runningThreads = 0;

private int Counter{
    get{
           lock(_lock) 
               return _runningThreads;
    }
    set{ 
           lock(_lock) 
               _runningThreads = value;
    }
}

Теперь, когда вы создаете рабочий поток, увеличивайте счетчик:

var t=new BackgroundWorker();
//ADD RunWorkerCompleted HANDLER
//START THREAD
Counter++;

В выполненной работе уменьшите счетчик:

private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    Counter--;
}

Теперь вы можете проверить счетчик в любое время, чтобы узнать, работает ли какой-либо поток:

if(Couonter>0){
    //SOME THREAD IS YET TO FINISH.
}

Ответ 6

В большинстве предлагаемых ответов не учитывается интервал времени ожидания, что очень важно для предотвращения возможного тупика. Далее мой образец кода. (Обратите внимание, что я, прежде всего, разработчик Win32, и как бы я это сделал.)

//'arrRunningThreads' = List<Thread>

//Wait for all threads
const int knmsMaxWait = 3 * 1000;           //3 sec timeout
int nmsBeginTicks = Environment.TickCount;
foreach(Thread thrd in arrRunningThreads)
{
    //See time left
    int nmsElapsed = Environment.TickCount - nmsBeginTicks;
    int nmsRemain = knmsMaxWait - nmsElapsed;
    if(nmsRemain < 0)
        nmsRemain = 0;

    //Then wait for thread to exit
    if(!thrd.Join(nmsRemain))
    {
        //It didn't exit in time, terminate it
        thrd.Abort();

        //Issue a debugger warning
        Debug.Assert(false, "Terminated thread");
    }
}

Ответ 7

Если вы не хотите использовать класс Task (например, в .NET 3.5), вы можете просто запустить все свои потоки, затем добавить их в список и присоединиться к ним в цикле foreach.

Пример:

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


// Start threads
for(int i = 0; i<10; i++)
{
    int tmp = i; // copy value for closure
    Thread t = new Thread(() => Console.WriteLine(tmp));
    t.Start;
    threads.Add(t);
}

// Await threads
foreach(Thread thread in threads)
{
    thread.Join();
}