I have some very simple code that's attempting to multi-thread an existing script.
On inspecting the treads window in visual Studio and calling Thread.CurrentThread.ManagedThreadId it always reports back as the same thread as starting the process. When ending it reports back a different thread id.
The threads do seem to be performing the task asynchronously, but the logging and output from visual studio are making me think otherwise.
Please could someone clarify what is going on and if I've made a mistake in my approach?
namespace ResolveGoogleURLs
{
class Program
{
public static void Main(string[] args)
{
HomeController oHC = new HomeController();
}
}
}
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ResolveGoogleURLs
{
class HomeController
{
public static int MaxJobs = 5;
public static int RecordsPerJob = 1000;
public static List<Task> TaskList = new List<Task>(MaxJobs);
public HomeController()
{
CreateJobs();
MonitorTasks();
}
public void MonitorTasks()
{
while (1 == 1)
{
Task.WaitAny(TaskList.ToArray());
TaskList.RemoveAll(x => x.IsCompleted);
Console.WriteLine("Task complete! Launching new...");
CreateJobs();
}
}
public async Task CreateJob()
{
Console.WriteLine("Thread {0} - Start", Thread.CurrentThread.ManagedThreadId);
// read in results from sql
await Task.Delay(10000);
Console.WriteLine("Thread {0} - End", Thread.CurrentThread.ManagedThreadId);
}
public void CreateJobs()
{
while (TaskList.Count < MaxJobs)
{
TaskList.Add( CreateJob() );
}
}
}
}
Output:
> Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 4 - End
Thread 5 - End
Thread 4 - End
Thread 6 - End
Thread 8 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 7 - End
Thread 6 - End
Thread 5 - End
Thread 4 - End
Thread 8 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 10 - End
Thread 9 - End
Task complete! Launching new...
Thread 1 - Start
Thread 7 - End
Thread 4 - End
Thread 6 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Tasks (class Task) are not the same as threads (class Thread).
Thread can be imagined as a virtual CPU that can run its code in same time than other threads. Every thread has its own stack (where local variables and function parameters are stored). In .NET a thread is mapped into a native thread - supported by the platform (operation system), that has things like thread kernel object, kernel mode stack, thread execution block, etc.. This makes thread a pretty heavy-weight object. Creation of new thread is time consuming and even when threads sleeps (doesn't execute its code), it still consumes a lot of memory (thread stack).
The operation system periodically goes through all running threads and based on their priority it assigns them a time slot, when a thread can use the real CPU (execute its code).
Because threads consume memory, they are slow to create and running too many threads hurts overall system performance, tasks (thread pools) were invented.
Task internally uses threads, because threads are the only way how to run code in parallel way in .NET. (Actually it's the only way how to run any code.) But it does it in efficient way.
When a .NET process is started, internally it creates a thread pool - a pool of threads that are used to execute tasks.
Task library is implemented in that way that when a process is started, a thread pool contains only a single thread. If you start to create new tasks, they are stored into a queue from which that single thread takes and execute one task after another. But .NET monitors if that thread is not overloaded with too many tasks - situation when tasks are waiting 'too long' in a thread pool queue. If - based on its internal criteria - it detects that initially created thread is overloaded, it creates a new one. So thread pool now has 2 threads and tasks can be ran on 2 of them in parallel way. This process can be repeated, so if a heavy load is present, thread pool can have 3, 4 or more threads.
On the other hand, if the frequency of new tasks drops down, and thread pool threads don't have tasks to execute, after 'some time' (what is 'some time' is defined by internal thread pool criteria) thread pool can decide to release some of threads - to conserve system resources. Number of thread pool threads can drop down to initially created single thread.
So task library internally uses threads, but it tries to use them in efficient way. Number of threads is scaled up and down based on the number of tasks program wants to execute.
In almost all cases tasks should be preferred solution to using 'raw' threads.
Related
I'm here because I'm having a weird behaviour using this code :
But before that, I KNOW THAT IS A REALLY BAD PRACTICE TO DO THAT, so it's not even used in reality I just want to understand what is happenning behind the scene but my knowledges are really poor about that.
Here is the code in question :
int worker = 0;
int io = 0;
Console.WriteLine($"Worker thread {worker} Io thread {io}");
ThreadPool.GetAvailableThreads(out worker, out io);
ThreadPool.GetMaxThreads(out var workerThreadsMax, out var completionPortThreadsMax);
Console.WriteLine($"Worker thread {workerThreadsMax - worker} Io thread {completionPortThreadsMax - io}");
for (int i = 0; i < 100; i++)
{
Task.Run(() =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - Running thread");
ThreadPool.GetAvailableThreads(out var worker2, out var io2);
ThreadPool.GetMaxThreads(out var workerThreadsMax2, out var completionPortThreadsMax2);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - Worker thread {workerThreadsMax2 - worker2} Io thread {completionPortThreadsMax2 - io2}");
var t1 = Task.Delay(5000);
var t2 = Task.Delay(5000);
Task.WaitAll(t1, t2);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " - End of thread");
ThreadPool.GetAvailableThreads(out worker2, out io2);
ThreadPool.GetMaxThreads(out workerThreadsMax2, out completionPortThreadsMax2);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - Worker thread {workerThreadsMax2 - worker2} Io thread {completionPortThreadsMax2 - io2}");
});
}
Console.ReadLine();
So what I'm trying to do in this code is to run or at least queue 500 tasks (that a lot I know, but was curious), while still displaying the number of active Threads from the ThreadPool. So in the first line I have 0 worker thread in the ThreadPool which makes sense, as long as I didn't start any Task yet. But when the first Task runs there is 8 active Threads. And here is where a weird thing is happening :
A new thread is spawned every second or less (but it's not instantly), which is not a real issue but the thing I don't understand is why the Task are blocked ? Even when the 250ms delay are finished the Task don't end itself, it still blocked on the Task.WaitAll line even after more than one minute :
Worker thread 0 Io thread 0
Worker thread 0 Io thread 0
8 - Running thread
8 - Worker thread 8 Io thread 0
6 - Running thread
6 - Worker thread 8 Io thread 0
10 - Running thread
10 - Worker thread 8 Io thread 0
7 - Running thread
7 - Worker thread 8 Io thread 0
11 - Running thread
11 - Worker thread 8 Io thread 0
5 - Running thread
9 - Running thread
9 - Worker thread 8 Io thread 0
12 - Running thread
12 - Worker thread 8 Io thread 0
5 - Worker thread 8 Io thread 0
13 - Running thread
13 - Worker thread 9 Io thread 0
14 - Running thread
14 - Worker thread 10 Io thread 0
15 - Running thread
15 - Worker thread 11 Io thread 0
16 - Running thread
16 - Worker thread 12 Io thread 0
17 - Running thread
17 - Worker thread 13 Io thread 0
18 - Running thread
18 - Worker thread 14 Io thread 0
Is there any deadlock happening here ? If can someone can explain me this, it would be really great. Thanks.
EDIT : For those who proposed to use async and await Task.WhenAll(..) you are totally right ! But as I said, it was for testing purpose and i won't do that in the reality and use instead the async/await statements for that. But we were testing something with a friend about synchronous and asynchronous Task and when testing the synchronous way we came across this problem withotu knowing what was happening. Thank you for those who clarified this. It has been very instructive.
Lets look at this piece of code in greater detail
var t1 = Task.Delay(5000);
var t2 = Task.Delay(5000);
Task.WaitAll(t1, t2);
Task.Delay is essentially a wrapper around a timer, more specifically a System.Threading.Timer. This timer will delegate the actual time keeping to the OS. When the timer has elapsed it will raise the even on a thread pool thread that in turn marks the task as completed. This will trigger a check for the Task.WhenAll task to see if that can complete, and if so unblock.
However, your test is essentially designed to exhaust the threadpool, Causing a classic deadlock. All the Task.WhenAll tasks are waiting for one or more Task.Delay to complete, but this requires an available threadpool thread, but all threads are blocked while waiting for the the Task.WhenAll tasks. So everything is waiting for something else, and nothing can run.
Except the designers of the threadpool anticipated this problem and added a mechanism that increases the number of threadpool threads, allowing the Task.Delay to complete, and resolve the deadlocks. But this mechanism is slow. So it should not be a surprise that there is a huge delay for tasks to complete.
Since this is a toy example a solution might not be required, but it might be worth repeating. Do not oversubscribe the threadpool. Use asynchronous, non-blocking code, or code that is careful about how many threads it uses.
Your Task should be:
Task.Run(async () =>
{
var t1 = Task.Delay(5000);
var t2 = Task.Delay(5000);
await Task.WhenAll(t1, t2);
});
Without the async, the method is blocking. The async/await makes the task non-blocking. As mentioned by Gurustron you need to use the non-blocking Task.WhenAll not, Task.WaitAll. See WaitAll vs WhenAll
I am creating an app that deals with huge number of data to be processed. I want to use threading in C# just to make it processes faster. Please see example code below.
private static void MyProcess(Object someData)
{
//Do some data processing
}
static void Main(string[] args)
{
for (int task = 1; task < 10; task++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(MyProcess), task);
}
}
Does this mean that a new thread will be created every loop passing the task to the "MyProcess" method (10 threads total)? Also, are the threads going to process concurrently?
The number of threads a threadpool will start depends on multiple factors, see The managed thread pool
Basically you are queing 10 worker items here which are likely to start threads immediatly.
The threads will most likly run concurrently, depending on the machine and number of processors.
If you start a large number of worker items, they will end up in a queue and start running as soon as a thread becomes available.
The calls will be scheduled on the thread pool. It does not guarantee that 10 threads will be created nor that all 10 tasks will be executed concurrently. The number of threads in the thread pool depends on the hardware and is chosen automatically to provide the best performance.
This articles contain good explanations of how it works:
https://owlcation.com/stem/C-ThreadPool-and-its-Task-Queue-Example
https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?redirectedfrom=MSDN&view=netframework-4.8
https://www.c-sharpcorner.com/article/thread-pool-in-net-core-and-c-sharp/
This Stackoverflow question explains the difference between ThreadPool and Thread:
Thread vs ThreadPool
Your method will be queued 9 times (you start at 1, not 0) for execution and will be executed when a thredpool thread will be available.
I have a Task which I do not await because I want it to continue its own logic in the background. Part of that logic is to delay 60 seconds and check back in to see if some minute work is to be done. The abbreviate code looks something like this:
public Dictionary<string, Task> taskQueue = new Dictionary<string, Task>();
// Entry point
public void DoMainWork(string workId, XmlDocument workInstructions){
// A work task (i.e. "workInstructions") is actually a plugin which might use its own tasks internally or any other logic it sees fit.
var workTask = Task.Factory.StartNew(() => {
// Main work code that interprets workInstructions
// .........
// .........
// etc.
}, TaskCreationOptions.LongRunning);
// Add the work task to the queue of currently running tasks
taskQueue.Add(workId, workTask);
// Delay a period of time and then see if we need to extend our timeout for doing main work code
this.QueueCheckinOnWorkTask(workId); // Note the non-awaited task
}
private async Task QueueCheckinOnWorkTask(string workId){
DateTime startTime = DateTime.Now;
// Delay 60 seconds
await Task.Delay(60 * 1000).ConfigureAwait(false);
// Find out how long Task.Delay delayed for.
TimeSpan duration = DateTime.Now - startTime; // THIS SOMETIMES DENOTES TIMES MUCH LARGER THAN EXPECTED, I.E. 80+ SECONDS VS. 60
if(!taskQueue.ContainsKey(workId)){
// Do something based on work being complete
}else{
// Work is not complete, inform outside source we're still working
QueueCheckinOnWorkTask(workId); // Note the non-awaited task
}
}
Keep in mind, this is example code just to show a extremely miniminal version of what is going on with my actual program.
My problem is that Task.Delay() is delaying for longer than the time specified. Something is blocking this from continuing in a reasonable timeframe.
Unfortunately I haven't been able to replicate the issue on my development machine and it only happens on the server every couple of days. Lastly, it seems related to the number of work tasks we have running at a time.
What would cause this to delay longer than expected? Additionally, how might one go about debugging this type of situation?
This is a follow up to my other question which did not receive an answer: await Task.Delay() delaying for longer that expected
Most often that happens because of thread pool saturation. You can clearly see its effect with this simple console application (I measure time the same way you are doing, doesn't matter in this case if we use stopwatch or not):
public class Program {
public static void Main() {
for (int j = 0; j < 10; j++)
for (int i = 1; i < 10; i++) {
TestDelay(i * 1000);
}
Console.ReadKey();
}
static async Task TestDelay(int expected) {
var startTime = DateTime.Now;
await Task.Delay(expected).ConfigureAwait(false);
var actual = (int) (DateTime.Now - startTime).TotalMilliseconds;
ThreadPool.GetAvailableThreads(out int aw, out _);
ThreadPool.GetMaxThreads(out int mw, out _);
Console.WriteLine("Thread: {3}, Total threads in pool: {4}, Expected: {0}, Actual: {1}, Diff: {2}", expected, actual, actual - expected, Thread.CurrentThread.ManagedThreadId, mw - aw);
Thread.Sleep(5000);
}
}
This program starts 100 tasks which await Task.Delay for 1-10 seconds, and then use Thread.Sleep for 5 seconds to simulate work on a thread on which continuation runs (this is thread pool thread). It will also output total number of threads in thread pool, so you will see how it increases over time.
If you run it you will see that in almost all cases (except first 8) - actual time after delay is much longer than expected, in some cases 5 times longer (you delayed for 3 seconds but 15 seconds has passed).
That's not because Task.Delay is so imprecise. The reason is continuation after await should be executed on a thread pool thread. Thread pool will not always give you a thread when you request. It can consider that instead of creating new thread - it's better to wait for one of the current busy threads to finish its work. It will wait for a certain time and if no thread became free - it will still create a new thread. If you request 10 thread pool threads at once and none is free, it will wait for Xms and create new one. Now you have 9 requests in queue. Now it will again wait for Xms and create another one. Now you have 8 in queue, and so on. This wait for a thread pool thread to become free is what causes increased delay in this console application (and most likely in your real program) - we keep thread pool threads busy with long Thread.Sleep, and thread pool is saturated.
Some parameters of heuristics used by thread pool are available for you to control. Most influential one is "minumum" number of threads in a pool. Thread pool is expected to always create new thread without delay until total number of threads in a pool reaches configurable "minimum". After that, if you request a thread, it might either still create new one or wait for existing to become free.
So the most straightforward way to remove this delay is to increase minimum number of threads in a pool. For example if you do this:
ThreadPool.GetMinThreads(out int wt, out int ct);
ThreadPool.SetMinThreads(100, ct); // increase min worker threads to 100
All tasks in the example above will complete at the expected time with no additional delay.
This is usually not recommended way to solve this problem though. It's better to avoid performing long running heavy operations on thread pool threads, because thread pool is a global resource and doing this affects your whole application. For example, if we remove Thread.Sleep(5000) in the example above - all tasks will delay for expected amount of time, because all what keeps thread pool thread busy now is Console.WriteLine statement which completes in no time, making this thread available for other work.
So to sum up: identify places where you perform heavy work on thread pool threads and avoid doing that (perform heavy work on separate, non-thread-pool threads instead). Alternatively, you might consider increasing minimum number of threads in a pool to a reasonable amount.
using System;
using System.Threading;
// Simple threading scenario: Start a static method running
// on a second thread.
public class ThreadExample {
// The ThreadProc method is called when the thread starts.
// It loops ten times, writing to the console and yielding
// the rest of its time slice each time, and then ends.
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// Yield the rest of the time slice.
Thread.Sleep(0);
}
}
public static void Main() {
Console.WriteLine("Main thread: Start a second thread.");
// The constructor for the Thread class requires a ThreadStart
// delegate that represents the method to be executed on the
// thread. C# simplifies the creation of this delegate.
Thread t = new Thread(new ThreadStart(ThreadProc));
// Start ThreadProc. Note that on a uniprocessor, the new
// thread does not get any processor time until the main thread
// is preempted or yields. Uncomment the Thread.Sleep that
// follows t.Start() to see the difference.
t.Start();
//Thread.Sleep(0);
for (int i = 0; i < 4; i++) {
Console.WriteLine("Main thread: Do some work.");
Thread.Sleep(0);
}
Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
t.Join();
Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");
Console.ReadLine();
}
}
The result is :
Main thread: Start a second thread.
Main thread: Do some work.
ThreadProc: 0
Main thread: Do some work.
ThreadProc: 1
Main thread: Do some work.
ThreadProc: 2
Main thread: Do some work.
ThreadProc: 3
Main thread: Call Join(), to wait until ThreadProc ends.
ThreadProc: 4
ThreadProc: 5
ThreadProc: 6
ThreadProc: 7
ThreadProc: 8
ThreadProc: 9
Main thread: ThreadProc.Join has returned. Press Enter to end program.
I don't understand why "ThreadProc 0" "1" "2" "3" can appear in between " Main thread: Do some work."
Can anybody help me explain it? thanks!
The "t.Start()" line starts the other thread which is running simultaneously while the main thread is doing it's work.
I think it would help you to read up about threads in general in computer science.
A thread (in my words) is an asynchronous unit of work. Your processor hops between different threads in your program to complete work at different intervals. The benefit you get is being able to perform work on one thread while another is waiting for something (like Thread.Sleep(0)). You also can utilize multiple core CPUs, since one core can execute one thread the same time as another.
Does that explain some?
The idea is that each thread is like a mini-process contained within the process of your program. The operating system then divides up the CPU execution time amongst them.
The calls to Sleep hasten the switching from one thread to the next. Without them, you might not see the intertwined output, but then again you might - the point is it's not really up to you when you make multiple threads which one executes and when - that's why you have to assume any thread might execute at any time and leads you into the concepts of locking and synchronization (which I'm sure you'll run into next or soon).
This seems correct. As soon as you start ThreadProc
t.Start();
it runs at the same time as main thread. So the System will print whichever print statement happens first. You will get the statements merged because both loops go at the same time.
MSDN saying that Thread.Start() method
Causes the operating system to change the state of the current
instance to ThreadState.Running.
Once a thread is in the ThreadState.Running state, the operating
system can schedule it for execution.
Output you've provided clearly shows that OS ran thread immediately and then switches control between both threads, try out
t.Priority = ThreadPriority.Lowest;
t.Start();
and see whether execution order has changed
Our scenario is a network scanner.
It connects to a set of hosts and scans them in parallel for a while using low priority background threads.
I want to be able to schedule lots of work but only have any given say ten or whatever number of hosts scanned in parallel. Even if I create my own threads, the many callbacks and other asynchronous goodness uses the ThreadPool and I end up running out of resources. I should look at MonoTorrent...
If I use THE ThreadPool, can I limit my application to some number that will leave enough for the rest of the application to Run smoothly?
Is there a threadpool that I can initialize to n long lived threads?
[Edit]
No one seems to have noticed that I made some comments on some responses so I will add a couple things here.
Threads should be cancellable both
gracefully and forcefully.
Threads should have low priority leaving the GUI responsive.
Threads are long running but in Order(minutes) and not Order(days).
Work for a given target host is basically:
For each test
Probe target (work is done mostly on the target end of an SSH connection)
Compare probe result to expected result (work is done on engine machine)
Prepare results for host
Can someone explain why using SmartThreadPool is marked wit ha negative usefulness?
In .NET 4 you have the integrated Task Parallel Library. When you create a new Task (the new thread abstraction) you can specify a Task to be long running. We have made good experiences with that (long being days rather than minutes or hours).
You can use it in .NET 2 as well but there it's actually an extension, check here.
In VS2010 the Debugging Parallel applications based on Tasks (not threads) has been radically improved. It's advised to use Tasks whenever possible rather than raw threads. Since it lets you handle parallelism in a more object oriented friendly way.
UPDATE
Tasks that are NOT specified as long running, are queued into the thread pool (or any other scheduler for that matter).
But if a task is specified to be long running, it just creates a standalone Thread, no thread pool is involved.
The CLR ThreadPool isn't appropriate for executing long-running tasks: it's for performing short tasks where the cost of creating a thread would be nearly as high as executing the method itself. (Or at least a significant percentage of the time it takes to execute the method.) As you've seen, .NET itself consumes thread pool threads, you can't reserve a block of them for yourself lest you risk starving the runtime.
Scheduling, throttling, and cancelling work is a different matter. There's no other built-in .NET worker-queue thread pool, so you'll have roll your own (managing the threads or BackgroundWorkers yourself) or find a preexisting one (Ami Bar's SmartThreadPool looks promising, though I haven't used it myself).
In your particular case, the best option would not be either threads or the thread pool or Background worker, but the async programming model (BeginXXX, EndXXX) provided by the framework.
The advantages of using the asynchronous model is that the TcpIp stack uses callbacks whenever there is data to read and the callback is automatically run on a thread from the thread pool.
Using the asynchronous model, you can control the number of requests per time interval initiated and also if you want you can initiate all the requests from a lower priority thread while processing the requests on a normal priority thread which means the packets will stay as little as possible in the internal Tcp Queue of the networking stack.
Asynchronous Client Socket Example - MSDN
P.S. For multiple concurrent and long running jobs that don't do allot of computation but mostly wait on IO (network, disk, etc) the better option always is to use a callback mechanism and not threads.
I'd create your own thread manager. In the following simple example a Queue is used to hold waiting threads and a Dictionary is used to hold active threads, keyed by ManagedThreadId. When a thread finishes, it removes itself from the active dictionary and launches another thread via a callback.
You can change the max running thread limit from your UI, and you can pass extra info to the ThreadDone callback for monitoring performance, etc. If a thread fails for say, a network timeout, you can reinsert back into the queue. Add extra control methods to Supervisor for pausing, stopping, etc.
using System;
using System.Collections.Generic;
using System.Threading;
namespace ConsoleApplication1
{
public delegate void CallbackDelegate(int idArg);
class Program
{
static void Main(string[] args)
{
new Supervisor().Run();
Console.WriteLine("Done");
Console.ReadKey();
}
}
class Supervisor
{
Queue<System.Threading.Thread> waitingThreads = new Queue<System.Threading.Thread>();
Dictionary<int, System.Threading.Thread> activeThreads = new Dictionary<int, System.Threading.Thread>();
int maxRunningThreads = 10;
object locker = new object();
volatile bool done;
public void Run()
{
// queue up some threads
for (int i = 0; i < 50; i++)
{
Thread newThread = new Thread(new Worker(ThreadDone).DoWork);
newThread.IsBackground = true;
waitingThreads.Enqueue(newThread);
}
LaunchWaitingThreads();
while (!done) Thread.Sleep(200);
}
// keep starting waiting threads until we max out
void LaunchWaitingThreads()
{
lock (locker)
{
while ((activeThreads.Count < maxRunningThreads) && (waitingThreads.Count > 0))
{
Thread nextThread = waitingThreads.Dequeue();
activeThreads.Add(nextThread.ManagedThreadId, nextThread);
nextThread.Start();
Console.WriteLine("Thread " + nextThread.ManagedThreadId.ToString() + " launched");
}
done = (activeThreads.Count == 0) && (waitingThreads.Count == 0);
}
}
// this is called by each thread when it's done
void ThreadDone(int threadIdArg)
{
lock (locker)
{
// remove thread from active pool
activeThreads.Remove(threadIdArg);
}
Console.WriteLine("Thread " + threadIdArg.ToString() + " finished");
LaunchWaitingThreads(); // this could instead be put in the wait loop at the end of Run()
}
}
class Worker
{
CallbackDelegate callback;
public Worker(CallbackDelegate callbackArg)
{
callback = callbackArg;
}
public void DoWork()
{
System.Threading.Thread.Sleep(new Random().Next(100, 1000));
callback(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
}
}
Use the built-in threadpool. It has good capabilities.
Alternatively you can look at the Smart Thread Pool implementation here or at Extended Thread Pool for a limit on the maximum number of working threads.