Как создать несколько потоков и дождаться их завершения?
Создайте несколько потоков и дождитесь их завершения.
Ответ 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();
}