你如何在C#中获得正在运行的线程列表?

时间:2012-02-22 15:28:16

标签: c# multithreading threadpool

我在C#中创建动态线程,我需要获取那些运行线程的状态。

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.
}

如何获得正在运行的线程列表?

5 个答案:

答案 0 :(得分:37)

On Threads

我会避免自己显式创建线程。

更可取的是使用ThreadPool.QueueUserWorkItem或者如果你可以使用.Net 4.0,你会得到更强大的Task parallel library,这也允许你使用更强大的ThreadPool线程方式(Task.Factory.StartNew值得一看)

如果我们选择明确创建线程的方法怎么办?

假设您的列表[0] .Count返回1000个项目。让我们假设您在高端(在撰写本文时)16核机器上执行此操作。直接影响是我们有1000个线程竞争这些有限的资源(16个核心)。

任务数量越多,每个任务运行的时间越长,在context switching中花费的时间就越多。此外,创建线程很昂贵,如果使用重用现有线程的方法,则可以避免显式创建每个线程的开销。

因此,虽然多线程的初始意图可能是增加速度,但正如我们所看到的那样,它可能具有相当相反的效果。

我们如何克服'过度'线程?

这是ThreadPool发挥作用的地方。

  

线程池是一组线程,可用于在后台执行许多任务。

它们如何运作:

  

一旦池中的线程完成其任务,它就会返回到等待线程的队列,在那里可以重用它。这种重用使应用程序能够避免为每个任务创建新线程的成本。

     

线程池通常具有最大线程数。如果所有线程都忙,则将其他任务放入队列,直到可以在线程可用时为其提供服务。

因此我们可以看到,通过使用线程池线程,我们两者都更有效率

  • 最大化实际工作而言。由于我们没有使用线程使处理器饱和,因此在线程之间切换所花费的时间更少,而实际执行线程应该执行的代码的时间更多。
  • 更快的线程启动:每个线程池线程都可用,而不是等到构建新线程。
  • 最小化内存消耗而言,线程池会将线程数限制为线程池大小,以排除任何超出线程池大小限制的请求。 (见ThreadPool.GetMaxThreads)。这种设计选择背后的主要原因当然是我们不会过多地使用过多的线程请求来保持有限数量的内核,从而将上下文切换保持在较低级别。
理论太多了,让我们把所有这些理论付诸实践!

是的,很高兴在理论上知道所有这些 ,但让我们把它付诸实践,看看是什么 数字告诉我们,应用程序的简化原始版本可以给我们粗略指示数量级的差异。我们将在新的Thread,ThreadPool和任务并行库(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.
            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();
    }

结果:

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();
    }

结果:

ThreadPool Benchmark

任务并行库(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();
    }

结果:

Task Parallel Library result

所以我们可以看到:

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

以上内容很符合我们理论上的预期。与ThreadPool相比,新线程的内存很高,整体性能也较慢。 ThreadPool和TPL具有相同的性能,TPL具有比纯线程池稍高的内存占用,但考虑到任务提供的额外灵活性(例如取消,等待完成查询任务状态),它可能是值得付出的代价

此时,我们已经证明使用ThreadPool线程是速度和内存方面的首选。

但是,我们还没有回答你的问题。如何跟踪运行的线程的状态。

回答你的问题

鉴于我们收集的见解,这就是我接近它的方式:

        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

作为最后一点,您可以在Thread.Start中使用变量i,因为它会在变化的变量上创建一个闭包,这个变量可以在所有线程之间共享。为了解决这个问题(假设您需要访问i),只需创建变量的副本并将副本传入,这将使每个线程一个闭包,这将使其线程安全。

祝你好运!

答案 1 :(得分:11)

使用Process.Threads

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

注意:当前进程拥有的所有线程都将显示在此处,包括那些未由您明确创建的线程。

如果您只想要创建的线程,那么,为什么不在创建它们时跟踪它们呢?

答案 2 :(得分:8)

创建一个List<Thread>并将每个新线程存储在您的第一个for循环中。

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()
}

但是,如果您不需要i,则可以将第二个循环设为foreach而不是for


作为旁注,如果你的sendMessage函数执行时间不长,你应该重量轻一些完整的线程,使用ThreadPool.QueueUserWorkItem或者如果它可用,{ {3}}

答案 3 :(得分:2)

Process.GetCurrentProcess().Threads

这为您提供了当前进程中运行的所有线程的列表,但要注意除了您自己创建的线程之外还有其他线程。

答案 4 :(得分:0)

使用Process.Threads遍历您的主题。