How can I create multiple threads and wait for all of them to complete?
8 Answers
It depends which version of the .NET Framework you are using. .NET 4.0 made thread management a whole lot easier using Tasks:
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
}
}
In previous versions of .NET you could use the BackgroundWorker
object, use ThreadPool.QueueUserWorkItem()
, or create your threads manually and use Thread.Join()
to wait for them to complete:
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");
}

- 20,442
- 6
- 58
- 76
-
7The Task API is by far the cleanest solution. – JefClaes Dec 07 '11 at 07:49
-
1There are some limitations to be aware of - if you require a thread to have a particular priority, then you can't use Tasks. Well, technically you could, but it is a bad idea to change thread priority of a thread inside Task because the thread belongs to a threadpool and so your custom priority could affect some other code. – JustAMartin May 22 '17 at 07:03
I think you need WaitHandler.WaitAll. Here is an example:
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();
}
FCL has a few more convenient functions.
(1) Task.WaitAll, as well as its overloads, when you want to do some tasks in parallel (and with no return values).
var tasks = new[]
{
Task.Factory.StartNew(() => DoSomething1()),
Task.Factory.StartNew(() => DoSomething2()),
Task.Factory.StartNew(() => DoSomething3())
};
Task.WaitAll(tasks);
(2) Task.WhenAll when you want to do some tasks with return values. It performs the operations and puts the results in an array. It's thread-safe, and you don't need to using a thread-safe container and implement the add operation yourself.
var tasks = new[]
{
Task.Factory.StartNew(() => GetSomething1()),
Task.Factory.StartNew(() => GetSomething2()),
Task.Factory.StartNew(() => GetSomething3())
};
var things = Task.WhenAll(tasks);

- 30,738
- 21
- 105
- 131

- 42,509
- 16
- 113
- 174
-
@Kirk: I was going to add an example just now but had to go to a meeting. – Cheng Chen Nov 16 '10 at 04:21
-
good explaination for 'no return' and 'return' values from method! – SHEKHAR SHETE Feb 23 '16 at 06:20
-
Your thread code was exactly what I was looking for since I wanted to simulate lots of requests, not just use the few threads the "smarter" TPL methods give me. – madannes May 12 '16 at 15:50
I've made a very simple extension method to wait for all threads of a collection:
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();
}
}
}
}
}
Then you simply call:
List<Thread> threads = new List<Thread>();
// Add your threads to this collection
threads.WaitAll();
-
I would rather use `ThreadHelpers.WaitAll(threadCollection)` .. in any case, this is largely what I use for tests. I've rarely had the need to 'wait all' in actual code. – user2864740 Dec 21 '16 at 16:53
-
An explanation would be in order. E.g., what is the principle of operation? – Peter Mortensen Aug 14 '20 at 01:56
In .NET 4.0, you can use the Task Parallel Library.
In earlier versions, you can create a list of Thread
objects in a loop, calling Start
on each one, and then make another loop and call Join
on each one.

- 30,738
- 21
- 105
- 131

- 868,454
- 176
- 1,908
- 1,964
-
2@user: If you call `Join` right after starting the thread, you'll end up waiting for it to finish before starting any other threads. You need to start _all_ of the threads, **then** `Join` all of them. – SLaks Nov 16 '10 at 13:32
If you don't want to use the Task class
(for instance, in .NET 3.5), you can just start all your threads, and then add them to the list and join them in a foreach
loop.
Example:
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);
}
// Join threads (wait threads)
foreach (Thread thread in threads) {
thread.Join();
}

- 179
- 3
- 12

- 59
- 1
- 1
I don't know if there is a better way, but the following describes how I did it with a counter and background worker thread.
private object _lock = new object();
private int _runningThreads = 0;
private int Counter{
get{
lock(_lock)
return _runningThreads;
}
set{
lock(_lock)
_runningThreads = value;
}
}
Now whenever you create a worker thread, increment the counter:
var t = new BackgroundWorker();
// Add RunWorkerCompleted handler
// Start thread
Counter++;
In work completed, decrement the counter:
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Counter--;
}
Now you can check for the counter anytime to see if any thread is running:
if(Couonter>0){
// Some thread is yet to finish.
}

- 30,738
- 21
- 105
- 131

- 40,053
- 20
- 133
- 188
-
1You don't need a lock. In fact, since you only write the property form the UI thread, you don't need anything. – SLaks Nov 16 '10 at 03:38
-
2I may be mistaken, but I think the lock is correct; he is decrementing the counter in an event handler that is fired when each worker completes. – Mark Avenius Nov 16 '10 at 03:48
-
2@Mark: The Completed event is always fired on the UI thread. Also, the lock won't have any effect; Int32 read and writes are atomic. Had there been thread issues, the lock wouldn't help; he'd need to call `Interlocked.Increment`. – SLaks Nov 16 '10 at 03:50
-
The lock won't work for Counter-- as the counter is unprotected between the get/set. – Greg Sansom Nov 16 '10 at 03:52
-
@Greg: Exactly. Multi-threading is hard. Slapping a lock in each member isn't nearly enough. – SLaks Nov 16 '10 at 04:04
-
Thanks for comments. I understand what @SLaks is saying and will use `Interlocked.Increment`, but @Greg how is counter unprotected? – TheVillageIdiot Nov 16 '10 at 04:04
-
@The: What if two threads run `Counter--` at once? This operation involves separate reads and writes; one of the decrements will be lost. The `Interlocked` class avoids this. – SLaks Nov 16 '10 at 04:06
-
However, since you're using BackgroundWorker, you don't need to worry about any of this; the Completed event always fires on the UI thread. – SLaks Nov 16 '10 at 04:06
-
thanks @SLaks I'll visit http://www.albahari.com/ to get a refresher on threading soon :D – TheVillageIdiot Nov 16 '10 at 04:48
-
-
Most proposed answers don't take into account a time-out interval, which is very important to prevent a possible deadlock. Next is my sample code. (Note that I'm primarily a Win32 developer, and that's how I'd do it there.)
//'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");
}
}

- 16,633
- 33
- 137
- 233
In my case, I could not instantiate my objects on the the thread pool with Task.Run()
or Task.Factory.StartNew()
. They would not synchronize my long running delegates correctly.
I needed the delegates to run asynchronously, pausing my main thread for their collective completion. The Thread.Join()
would not work since I wanted to wait for collective completion in the middle of the parent thread, not at the end.
With the Task.Run()
or Task.Factory.StartNew()
, either all the child threads blocked each other or the parent thread would not be blocked, ... I couldn't figure out how to go with async
delegates because of the re-serialization of the await
syntax.
Here is my solution using Threads instead of Tasks:
using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.ManualReset))
{
int outdex = mediaServerMinConnections - 1;
for (int i = 0; i < mediaServerMinConnections; i++)
{
new Thread(() =>
{
sshPool.Enqueue(new SshHandler());
if (Interlocked.Decrement(ref outdex) < 1)
wh.Set();
}).Start();
}
wh.WaitOne();
}

- 30,738
- 21
- 105
- 131

- 1,268
- 15
- 27