How do you get list of running threads in C#?

32,731

Solution 1

Create a List<Thread> and store each new thread in your first for loop in it.

List<string>[] list;
List<Thread> threads = new List<Thread>();
list = dbConnect.Select();

for (int i = 0; i < list[0].Count; i++)
{
    Thread th = new Thread(() =>{
        sendMessage(list[0]['1']);
        //calling callback function
    });
    th.Name = "SID"+i;
    th.Start();
    threads.add(th)
}

for (int i = 0; i < list[0].Count; i++)
{
    threads[i].DoStuff()
}

However if you don't need i you can make the second loop a foreach instead of a for


As a side note, if your sendMessage function does not take very long to execute you should somthing lighter weight then a full Thread, use a ThreadPool.QueueUserWorkItem or if it is available to you, a Task

Solution 2

On Threads

I would avoid explicitly creating threads on your own.

It is much more preferable to use the ThreadPool.QueueUserWorkItem or if you do can use .Net 4.0 you get the much more powerful Task parallel library which also allows you to use a ThreadPool threads in a much more powerful way (Task.Factory.StartNew is worth a look)

What if we choose to go by the approach of explicitly creating threads?

Let's suppose that your list[0].Count returns 1000 items. Let's also assume that you are performing this on a high-end (at the time of this writing) 16core machine. The immediate effect is that we have 1000 threads competing for these limited resources (the 16 cores).

The larger the number of tasks and the longer each of them runs, the more time will be spent in context switching. In addition, creating threads is expensive, this overhead creating each thread explicitly could be avoided if an approach of reusing existing threads is used.

So while the initial intent of multithreading may be to increase speed, as we can see it can have quite the opposite effect.

How do we overcome 'over'-threading?

This is where the ThreadPool comes into play.

A thread pool is a collection of threads that can be used to perform a number of tasks in the background.

How do they work:

Once a thread in the pool completes its task, it is returned to a queue of waiting threads, where it can be reused. This reuse enables applications to avoid the cost of creating a new thread for each task.

Thread pools typically have a maximum number of threads. If all the threads are busy, additional tasks are placed in queue until they can be serviced as threads become available.

So we can see that by using a thread pool threads we are more efficient both

  • in terms of maximizing the actual work getting done. Since we are not over saturating the processors with threads, less time is spent switching between threads and more time actually executing the code that a thread is supposed to do.
  • Faster thread startup: Each threadpool thread is readily available as opposed to waiting until a new thread gets constructed.
  • in terms of minimising memory consumption, the threadpool will limit the number of threads to the threadpool size enqueuing any requests that are beyond the threadpool size limit. (see ThreadPool.GetMaxThreads). The primary reason behind this design choice, is of course so that we don't over-saturate the limited number of cores with too many thread requests keeping context switching to lower levels.

Too much Theory, let's put all this theory to the test!

Right, it's nice to know all this in theory, but let's put it to practice and see what the numbers tell us, with a simplified crude version of the application that can give us a coarse indication of the difference in orders of magnitude. We will do a comparison between new Thread, ThreadPool and Task Parallel Library (TPL)

new Thread

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i;  // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            Thread thread = new Thread(() =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });

            thread.Name = "SID" + iCopy; // you can also use i here. 
            thread.Start();
        }

        Console.ReadKey();
        Console.WriteLine(GC.GetTotalMemory(false) - initialMemoryFootPrint);
        Console.ReadKey();
    }

Result:

New Thread Benchmark

ThreadPool.EnqueueUserWorkItem

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();

        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            ThreadPool.QueueUserWorkItem((w) =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) 
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });
        }

        Console.ReadKey();
        Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
        Console.ReadKey();
    }

Result:

ThreadPool Benchmark

Task Parallel Library (TPL)

    static void Main(string[] args)
    {
        int itemCount = 1000;

        Stopwatch stopwatch = new Stopwatch(); 
        long initialMemoryFootPrint = GC.GetTotalMemory(true);

        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            int iCopy = i;  // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable.
            Task.Factory.StartNew(() =>
            {
                // lets simulate something that takes a while
                int k = 0;
                while (true)
                {
                    if (k++ > 100000)
                        break;
                }

                if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe.
                    Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds);
            });
        }

        Console.ReadKey();
        Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint));
        Console.ReadKey();
    }

Result:

Task Parallel Library result

So we can see that:

+--------+------------+------------+--------+
|        | new Thread | ThreadPool |  TPL   |
+--------+------------+------------+--------+
| Time   | 6749       | 228ms      | 222ms  |
| Memory | ≈300kb     | ≈103kb     | ≈123kb |
+--------+------------+------------+--------+

The above falls nicely inline to what we anticipated in theory. High memory for new Thread as well as slower overall performance when compared to ThreadPool. ThreadPool and TPL have equivalent performance with TPL having a slightly higher memory footprint than a pure thread pool but it's probably a price worth paying given the added flexibility Tasks provide (such as cancellation, waiting for completion querying status of task)

At this point, we have proven that using ThreadPool threads is the preferable option in terms of speed and memory.

Still, we have not answered your question. How to track the state of the threads running.

To answer your question

Given the insights we have gathered, this is how I would approach it:

        List<string>[] list = listdbConnect.Select()
        int itemCount = list[0].Count;
        Task[] tasks = new Task[itemCount];
        stopwatch.Start();
        for (int i = 0; i < itemCount; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {
                // NOTE: Do not use i in here as it is not thread safe to do so! 
                sendMessage(list[0]['1']);
                //calling callback function
            });
        }

        // if required you can wait for all tasks to complete
        Task.WaitAll(tasks);
        
        // or for any task you can check its state with properties such as: 
        tasks[1].IsCanceled
        tasks[1].IsCompleted
        tasks[1].IsFaulted 
        tasks[1].Status

As a final note, you can not use the variable i in your Thread.Start, since it would create a closure over a changing variable which would effectively be shared amongst all Threads. To get around this (assuming you need to access i), simply create a copy of the variable and pass the copy in, this would make one closure per thread which would make it thread safe.

Good luck!

Solution 3

Use Process.Threads:

var currentProcess = Process.GetCurrentProcess();
var threads = currentProcess.Threads;

Note: any threads owned by the current process will show up here, including those not explicitly created by you.

If you only want the threads that you created, well, why don't you just keep track of them when you create them?

Solution 4

Process.GetCurrentProcess().Threads

This gives you a list of all threads running in the current process, but beware that there are threads other than those you started yourself.

Solution 5

Use Process.Threads to iterate through your threads.

Share:
32,731
Shiv Kumar Sah
Author by

Shiv Kumar Sah

Open Source Enthusiast, Full stack developer, Loves P (Python, PHP, Perl), Play with MongoDB and MySQL... Twitter me @ shivkumarsah

Updated on January 10, 2020

Comments

  • Shiv Kumar Sah
    Shiv Kumar Sah over 4 years

    I create dynamic threads in C# and I need to get the status of those running threads.

    List<string>[] list;
    list = dbConnect.Select();
    
    for (int i = 0; i < list[0].Count; i++)
    {
        Thread th = new Thread(() =>{
            sendMessage(list[0]['1']);
            //calling callback function
        });
        th.Name = "SID"+i;
        th.Start();
    }
    
    for (int i = 0; i < list[0].Count; i++)
    {
        // here how can i get list of running thread here.
    }
    

    How can you get list of running threads?

    • sll
      sll about 12 years
      You need list of threads you've created or all threads?
  • Stefan Paul Noack
    Stefan Paul Noack about 12 years
    This is probably the best solution to the problem.
  • spender
    spender about 12 years
    Yep, good, but Thread doesn't have a DoStuff method (but I suppose it could be implemented with an extension method)
  • Scott Chamberlain
    Scott Chamberlain about 12 years
    @spender Yea, I did not know what the OP wanted to do with it so that was my thought processes when I wrote it.
  • Kiril
    Kiril about 12 years
    @spender DoStuff as in doing stuff like Start, Interrupt, Join, etc.
  • Riegardt Steyn
    Riegardt Steyn about 10 years
    This could be slightly misleading. var threads in this case is a ProcessThreadCollection, which purely contains information on the threads, not the Thread objects themselves.
  • sebingel
    sebingel over 8 years
    thank you very much. by far one of the best answers given to this topic i have ever read.