C# 中的线程池

C# 中的线程池

在本文中,我将通过示例讨论C# 中的线程池。请阅读我们之前的文章,其中我们讨论了C# 中**多线程应用程序的性能测试** **。**作为本文的一部分,我们将讨论以下几点。

  1. 线程的请求生命周期。
  2. C# 中的线程池
  3. 普通线程和线程池之间的性能测试

一、C# 中线程的请求生命周期及其示例。

​ 当.NET框架收到请求时(该请求可以是来自任何类型的应用程序的方法调用或函数调用)。为了处理该请求,创建了一个线程对象。创建线程对象时,一些资源会分配给该线程对象,例如内存。然后执行任务,一旦任务完成,垃圾收集器就会删除该线程对象以释放内存分配。这就是C#中线程的生命周期。

C# 中线程的请求生命周期及其示例

​ 对于多线程应用程序中出现的每个请求,将一次又一次地重复这些步骤。这意味着每次创建新的线程对象时,都会在内存中分配它。如果有很多请求,那么就会有很多线程对象,如果有很多线程对象,那么内存上就会有负载,这会减慢应用程序的速度。性能还有很大的提升空间。创建了 Thread 对象,分配了资源,执行了任务,然后它不应该进行垃圾回收,而不是像下图所示获取线程对象并将其放入池中?这就是线程池发挥作用的地方。

C# 中的线程池

二、C# 中的线程池:

​ C# 中的线程池只不过是线程的集合,可以重用这些线程来在后台执行许多任务。现在当有请求到来时,它会直接进入线程池并检查是否有可用的空闲线程。如果可用,则它从线程池中获取线程对象并执行任务,如下图所示。
在这里插入图片描述
一旦线程完成其任务,它就会再次被发送回线程池,以便可以重用。这种可重用性避免了应用程序创建多个线程,从而减少了内存消耗。

三、普通线程和线程池之间的性能测试

​ 如下面的代码所示,在这里,创建了一个方法 MyMethod,作为该方法的一部分,只需打印线程 id、线程是否为后台线程以及是否来自线程池或。使用线程池线程执行此方法 10 次。

3.1 线程池实现:

using System;
using System.Threading;

namespace ThreadPoolApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(MyMethod));
            }
            Console.Read();
        }

        public static void MyMethod(object obj)
        {
            Thread thread = Thread.CurrentThread;
            string message = $"Background: {thread.IsBackground}, Thread Pool: {thread.IsThreadPoolThread}, Thread ID: {thread.ManagedThreadId}";
            Console.WriteLine(message);
        }
    }
}

​ 执行上述代码后,它将给出以下输出。表明它是后台线程,并且线程来自线程池,并且输出中的线程 ID 可能有所不同。在这里,一些线程被重用,这就是在 C# 中使用线程池的优点。

了解 C# 中线程池的完整示例代码

3. 2 普通线程实现:

using System;
using System.Threading;

namespace ThreadPoolApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(MyMethod)
                {
                    Name = "Thread" + i
                };
                thread.Start();
            }
            Console.Read();
        }

        public static void MyMethod(object obj)
        {
            Thread thread = Thread.CurrentThread;
            string message = $"Background: {thread.IsBackground}, Thread Pool: {thread.IsThreadPoolThread}, Thread ID: {thread.ManagedThreadId}";
            Console.WriteLine(message);
        }
    }
}

​ 运行上面的代码,您将得到以下输出。在这里,线程不是后台线程,它们也不是来自线程池,并且线程也没有被重用。每个线程都有一个唯一的线程ID。

C# 中的线程池

3.3 线程与线程池性能对比

using System;
using System.Diagnostics;
using System.Threading;

namespace ThreadPoolApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            //Warmup Code start
            for (int i = 0; i < 10; i++)
            {
                MethodWithThread();
                MethodWithThreadPool();
            }
            //Warmup Code start
            Stopwatch stopwatch = new Stopwatch();

            Console.WriteLine("Execution using Thread");
            stopwatch.Start();
            MethodWithThread();
            stopwatch.Stop();
            Console.WriteLine("Time consumed by MethodWithThread is : " +
                                 stopwatch.ElapsedTicks.ToString());
            
            stopwatch.Reset();

            Console.WriteLine("Execution using Thread Pool");
            stopwatch.Start();
            MethodWithThreadPool();
            stopwatch.Stop();
            Console.WriteLine("Time consumed by MethodWithThreadPool is : " +
                                 stopwatch.ElapsedTicks.ToString());
            
            Console.Read();
        }

        public static void MethodWithThread()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(Test);
                thread.Start();
            }
        }

        public static void MethodWithThreadPool()
        {
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(Test));
            }           
        }

        public static void Test(object obj)
        {
        }       
    }
}

C# 中的线程池时间基准
​ 正如输出中看到的,MethodWithThread 消耗的时间是 272588,而 MethodWithThreadPool 消耗的时间是 692。这两者之间存在巨大的时间差异。因此,它证明与线程类对象相比,线程池提供了更好的性能。如果需要创建一两个线程,那么需要使用Thread类对象,而如果需要创建超过5个线程,那么需要在多线程环境中使用线程池类。