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

Shi*_*Sah 6 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.
}
Run Code Online (Sandbox Code Playgroud)

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

Ana*_*yal 37

在线程上

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

更可取的是使用ThreadPool.QueueUserWorkItem或如果你可以使用.Net 4.0你会得到更强大的任务并行库,它也允许你以更强大的方式使用ThreadPool线程(Task.Factory.StartNew值得一看)

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

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

任务数量越大,每个任务运行的时间越长,在上下文切换中花费的时间就越多.此外,创建线程非常昂贵,如果使用重用现有线程的方法,则可以避免显式创建每个线程的开销.

因此,虽然多线程的初始意图可能是提高速度,但正如我们所看到的,它可能会产生相反的效果.

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

这是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();
    }
Run Code Online (Sandbox Code Playgroud)

结果:

新线程基准

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();
    }
Run Code Online (Sandbox Code Playgroud)

结果:

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.
            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();
    }
Run Code Online (Sandbox Code Playgroud)

结果:

任务并行库结果

所以我们可以看到:

+--------+------------+------------+--------+
|        | new Thread | ThreadPool |  TPL   |
+--------+------------+------------+--------+
| Time   | 6749       | 228ms      | 222ms  |
| Memory | ?300kb     | ?103kb     | ?123kb |
+--------+------------+------------+--------+
Run Code Online (Sandbox Code Playgroud)

以上内容很符合我们理论上的预期.与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
Run Code Online (Sandbox Code Playgroud)

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

祝好运!


jas*_*son 11

用途Process.Threads:

var currentProcess = Process.GetCurrentProcess();
var threads = currentProcess.Threads;
Run Code Online (Sandbox Code Playgroud)

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

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


Sco*_*ain 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()
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您不需要i,可以使第二个循环foreach而不是afor


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