If you are tighten to .Net 2.0 you can use the following technique:
knowing the fact that whey you enqueue a task to the ThreadPool it will create a new thread (of course if there're no free ones), you'll wait before doing this until there's a free thread. For this purpose the BlockingCounter class is used (described below) that once the limit is reached will wait to increment until someone (another thread) decrements it.
Below is the sample that shows a maximum of 4 tasks with the total number of 10. ManualResetEvents are used to check if your tasks are finished (maybe that's not so good when there's a huge number of tasks to do, but it's up to you how to improve it).
class Program { static int s_numCurrentThreads = 0; static Random s_rnd = new Random(); static void Main(string[] args) { int maxParallelTasks = 4; int totalTasks = 10; BlockingCounter blockingCounter = new BlockingCounter(maxParallelTasks); List<ManualResetEvent> waitList = new List<ManualResetEvent>(); for (int i = 1; i <= totalTasks; i++) { ManualResetEvent finishEvent = new ManualResetEvent(false); waitList.Add(finishEvent); Console.WriteLine("Submitting task {0}",i); blockingCounter.WaitableIncrement(); if (!ThreadPool.QueueUserWorkItem((obj) => { try { ThreadProc(obj); } catch(Exception ex) { Console.Error.WriteLine("Task {0} failed: {1}", obj, ex.Message); } finally { // Exceptions are possible here too, // but proper error handling is not the goal of this sample finishEvent.Set(); blockingCounter.WaitableDecrement(); } }, i)) { waitList.Remove(finishEvent); blockingCounter.WaitableDecrement(); Console.Error.WriteLine("Failed to submit task {0} for execution.", i); } } blockingCounter.Close(); Console.WriteLine("Waiting for copmletion..."); foreach (ManualResetEvent finishEvent in waitList) { finishEvent.WaitOne(10000); } Console.WriteLine("Work done!"); Console.ReadKey(); } static void ThreadProc (object obj) { int taskNumber = (int) obj; int numThreads = Interlocked.Increment(ref s_numCurrentThreads); Console.WriteLine("Task {0} started. Total: {1}", taskNumber, numThreads); int sleepTime = s_rnd.Next(0, 5); Thread.Sleep(sleepTime * 1000); Console.WriteLine("Task {0} finished.", taskNumber); Interlocked.Decrement(ref s_numCurrentThreads); } It uses the BlockingCounter class that is based on the Marc Gravell's SizeQueue posted here, but without a counter instead of a queue.
public class BlockingCounter { private int m_Count; private object m_counterLock = new object(); private bool m_isClosed = false; private int m_MaxSize = 0; public BlockingCounter(int maxSize = 0) { if (maxSize < 0) throw new ArgumentOutOfRangeException("maxSize"); m_MaxSize = maxSize; } public void WaitableIncrement(int timeoutMs = Timeout.Infinite) { lock (m_counterLock) { CheckClosed(); while (m_MaxSize > 0 && m_Count >= m_MaxSize) { Monitor.Wait(m_counterLock, timeoutMs); } m_Count++; if (m_Count == 1) { Monitor.PulseAll(m_counterLock); } } } public void WaitableDecrement(int timeoutMs = Timeout.Infinite) { lock (m_counterLock) { while (m_Count == 0) { CheckClosed(); Monitor.Wait(m_counterLock, timeoutMs); } m_Count--; if (m_MaxSize == 0 || m_Count == m_MaxSize - 1) Monitor.PulseAll(m_counterLock); } } void CheckClosed() { if (m_isClosed) throw new Exception("The counter is closed");// <-- throw any custom exception here if needed } public void Close() { lock (m_counterLock) { m_isClosed = true; Monitor.PulseAll(m_counterLock); } } } The result will be like that:
Submitting task 1 Submitting task 2 Submitting task 3 Submitting task 4 Submitting task 5 Task 1 started. Total: 1 Task 1 finished. Task 3 started. Total: 1 Submitting task 6 Task 2 started. Total: 2 Task 3 finished. Task 6 started. Total: 4 Task 5 started. Total: 3 Task 4 started. Total: 4 Submitting task 7 Task 4 finished. Submitting task 8 Task 7 started. Total: 4 Task 5 finished. Submitting task 9 Task 7 finished. Task 8 started. Total: 4 Task 9 started. Total: 4 Submitting task 10 Task 2 finished. Waiting for copmletion... Task 10 started. Total: 4 Task 10 finished. Task 6 finished. Task 8 finished. Task 9 finished. Work done!