.NET基础拾遗(7)多线程开发基础2

二、.NET中的多线程编程

2.1 如何在.NET程序中手动控制多个线程?

   最直接且灵活性最大的,莫过于主动创建、运行、结束所有线程。

  (1)第一个多线程程序

  .NET提供了非常直接的控制线程类型的类型:System.Threading.Thread类。下面是一个简单的多线程程序:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("进入多线程工作模式:");
            for (int i = 0; i < 10; i++)
            {
                Thread newThread = new Thread(Work);
                // 开启新线程
                newThread.Start();
            }

            Console.ReadKey();
        }

        static void Work()
        {
            Console.WriteLine("线程开始");
            // 模拟做了一些工作,耗费1s时间
            Thread.Sleep(1000);
            Console.WriteLine("线程结束");
        }
    }

  在主线程中,该代码创建了10个新的线程,这个10个线程的工作互不干扰,宏观上来看它们应该是并行运行的,执行的结果也证实了这一点:

  

PS:当new了一个Thread类型对象并不意味着生成了一个线程,线程的生成是在调用Thread的Start方法的时候。另外这里的线程并不一定是操作系统层面上产生的一个真正线程!

  (2)控制线程的状态

  在任意时刻,.NET中的线程都会处于如下图所示的几个状态中的某一个状态上,该图也直观地展示了一个线程可能经过的状态转换过程

  下面的示例代码则展示了我们如何手动地查看和控制一个线程的状态:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("开始测试线程1");
            // 初始化一个线程 thread1
            Thread thread1 = new Thread(Work1);
            // 这时状态:UnStarted
            PrintState(thread1);
            // 启动线程
            Console.WriteLine("现在启动线程");
            thread1.Start();
            // 这时状态:Running
            PrintState(thread1);
            // 让线程飞一会 3s
            Thread.Sleep(3 * 1000);
            // 让线程挂起
            Console.WriteLine("现在挂起线程");
            thread1.Suspend();
            // 给线程足够的时间来挂起,否则状态可能是SuspendRequested
            Thread.Sleep(1000);
            // 这时状态:Suspend
            PrintState(thread1);
            // 继续线程
            Console.WriteLine("现在继续线程");
            thread1.Resume();
            // 这时状态:Running
            PrintState(thread1);
            // 停止线程
            Console.WriteLine("现在停止线程");
            thread1.Abort();
            // 给线程足够的时间来终止,否则的话可能是AbortRequested
            Thread.Sleep(1000);
            // 这时状态:Stopped
            PrintState(thread1);
            Console.WriteLine("------------------------------");
            Console.WriteLine("开始测试线程2");
            // 初始化一个线程 thread2
            Thread thread2 = new Thread(Work2);
            // 这时状态:UnStarted
            PrintState(thread2);
            // 启动线程
            thread2.Start();
            Thread.Sleep(2 * 1000);
            // 这时状态:WaitSleepJoin
            PrintState(thread2);
            // 给线程足够的时间结束
            Thread.Sleep(10 * 1000);
            // 这时状态:Stopped
            PrintState(thread2);

            Console.ReadKey();
        }

        // 普通线程方法:一直在运行从未被超越
        private static void Work1()
        {
            Console.WriteLine("线程运行中...");
            // 模拟线程运行,但不改变线程状态
            // 采用忙等状态
            while (true) { }
        }

        // 文艺线程方法:运行10s就结束
        private static void Work2()
        {
            Console.WriteLine("线程开始睡眠:");
            // 睡眠10s
            Thread.Sleep(10 * 1000);
            Console.WriteLine("线程恢复运行");
        }

        // 打印线程的状态
        private static void PrintState(Thread thread)
        {
            Console.WriteLine("线程的状态是:{0}", thread.ThreadState.ToString());
        }
    }

  上述代码的执行结果如下图所示:

PS:在.NET Framework 4.0 及之后的版本中,已不鼓励使用线程的挂起状态及Suspend和Resume方法了。

2.2 如何使用.NET中的线程池?

  (1).NET中的线程池是神马

  线程的创建和销毁需要很大的性能开销,在Windows NT内核的操作系统中,每个进程都会包含一个线程池。

而在.NET中呢,也有自己的线程池,它是由CLR负责管理的。

  线程池相当于一个缓存的概念,在该池中已经存在了一些没有被销毁的线程,而当应用程序需要一个新的线程时,就可以从线程池中直接获取一个已经存在的线程。相对应的,当一个线程被使用完毕后并不会立刻被销毁,而是放入线程池中等待下一次使用

PS:线程池中运行的线程均为后台线程(即线程的 IsBackground 属性被设为true),所谓的后台线程是指这些线程的运行不会阻碍应用程序的结束。相反的,应用程序的结束则必须等待所有前台线程结束后才能退出。

  (2)在.NET中使用线程池

  在.NET中通过 System.Threading.ThreadPool 类型来提供关于线程池的操作,ThreadPool 类型提供了几个静态方法允许使用者插入一个工作线程的需求。常用的有以下三个静态方法:

  ① static bool QueueUserWorkItem(WaitCallback callback)

  ② static bool QueueUserWorkItem(WaitCallback callback, Object state)

  ③ static bool UnsafeQueueUserWorkItem(WaitCallback callback, Object state)

  有了这几个方法,我们只需要将线程要处理的方法作为参数传入上述方法即可,随后的工作都由CLR的线程池管理程序来完成。其中,WaitCallback 是一个委托类型,该委托方法接受一个Object类型的参数且没有返回值。下面的代码展示了如何使用线程池来编写多线程的程序:

    class Program
    {
        static void Main(string[] args)
        {
            string taskInfo = "运行10秒";
            // 插入一个新的请求到线程池
            bool result = ThreadPool.QueueUserWorkItem(DoWork, taskInfo);
            // 分配线程有可能会失败
            if (!result)
            {
                Console.WriteLine("分配线程失败");
            }
            else
            {
                Console.WriteLine("按回车键结束程序");
            }

            Console.ReadKey();
        }

        private static void DoWork(object state)
        {
            // 模拟做了一些操作,耗时10s
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("工作者线程的任务是:{0}", state);
                Thread.Sleep(1000);
            }
        }
    }

  上述代码执行后,如果不输入任何字符,那么会得到如下图所示的执行结果:

PS:事实上,UnsafeQueueWorkItem方法实现了完全相同的功能,二者的差别在于UnsafeQueueWorkItem方法不会将调用线程的堆栈传递给辅助线程,这就意味着主线程的权限限制不会传递给辅助线程。UnsafeQueueWorkItem由于不进行这样的传递,因此会得到更高的运行效率,但是潜在地提升了辅助线程的权限,也就有可能会成为一个潜在的安全漏洞。

2.3 如何查看和设置线程池的上下限?

  通常情况下,我们无需修改默认的配置。但在一些场合,我们可能需要了解线程池的上下限和剩余的线程数。

线程池作为一个缓冲池,有着其上下限。在通常情况下,当线程池中的线程数小于线程池设置的下限时,线程池会设法创建新的线程,而当线程池中的线程数大于线程池设置的上限时,线程池将销毁多余的线程

PS:在.NET Framework 4.0中,每个CPU默认的工作者线程数量最大值为250个,最小值为2个。而IO线程的默认最大值为1000个,最小值为2个。

  在.NET中,通过 ThreadPool 类型提供的5个静态方法可以获取和设置线程池的上限和下限,同时它还额外地提供了一个方法来让程序员获知当前可用的线程数量,下面是这五个方法的签名:

  ① static void GetMaxThreads(out int workerThreads, out int completionPortThreads)

  ② static void GetMinThreads(out int workerThreads, out int completionPortThreads)

  ③ static bool SetMaxThreads(int workerThreads, int completionPortThreads)

  ④ static bool SetMinThreads(int workerThreads, int completionPortThreads)

  ⑤ static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)

  下面的代码示例演示了如何查询线程池的上下限阈值和可用线程数量:

    class Program
    {
        static void Main(string[] args)
        {
            // 打印阈值和可用数量
            GetLimitation();
            GetAvailable();

            // 使用掉其中三个线程
            Console.WriteLine("此处申请使用3个线程...");
            ThreadPool.QueueUserWorkItem(Work);
            ThreadPool.QueueUserWorkItem(Work);
            ThreadPool.QueueUserWorkItem(Work);

            Thread.Sleep(1000);

            // 打印阈值和可用数量
            GetLimitation();
            GetAvailable();
            // 设置最小值
            Console.WriteLine("此处修改了线程池的最小线程数量");
            ThreadPool.SetMinThreads(10, 10);
            // 打印阈值
            GetLimitation();

            Console.ReadKey();
        }

        // 运行10s的方法
        private static void Work(object o)
        {
            Thread.Sleep(10 * 1000);
        }

        // 打印线程池的上下限阈值
        private static void GetLimitation()
        {
            int maxWork, minWork, maxIO, minIO;
            // 得到阈值上限
            ThreadPool.GetMaxThreads(out maxWork, out maxIO);
            // 得到阈值下限
            ThreadPool.GetMinThreads(out minWork, out minIO);
            // 打印阈值上限
            Console.WriteLine("线程池最多有{0}个工作者线程,{1}个IO线程", maxWork.ToString(), maxIO.ToString());
            // 打印阈值下限
            Console.WriteLine("线程池最少有{0}个工作者线程,{1}个IO线程", minWork.ToString(), minIO.ToString());
            Console.WriteLine("------------------------------------");
        }

        // 打印可用线程数量
        private static void GetAvailable()
        {
            int remainWork, remainIO;
            // 得到当前可用线程数量
            ThreadPool.GetAvailableThreads(out remainWork, out remainIO);
            // 打印可用线程数量
            Console.WriteLine("线程池中当前有{0}个工作者线程可用,{1}个IO线程可用", remainWork.ToString(), remainIO.ToString());
            Console.WriteLine("------------------------------------");
        }
    }

  该实例的执行结果如下图所示:

PS:上面代码示例在不同的计算机上运行可能会得到不同的结果,线程池中的可用数码不会再初始时达到最大值,事实上CLR会尝试以一定的时间间隔来逐一地创建新线程,但这个时间间隔非常短。

时间: 2024-10-05 04:11:41

.NET基础拾遗(7)多线程开发基础2的相关文章

.NET基础拾遗(5)多线程开发基础

Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 一.多线程编程的基本概念 下面的一些基本概念可能和.NET的联系并不大,但对于掌握.NET中的多线程开发来说却十分重要.我们在开始尝试多线程开发前,应该对这些基础知识有所掌握,并且能够在操作系统层面理解多线程的运行方式. 1.1 操作系统层面的进程和线程 (1)进程 进程代表了操作系统上运行着的一个应用程序.进程拥有自己的程序块

.NET基础拾遗(7)多线程开发基础4

一.多线程编程中的线程同步 1.C#中的lock关键字 lock关键字可能是我们在遇到线程同步的需求时最常用的方式,但lock只是一个语法糖,为什么这么说呢,下面慢慢道来. (1)lock的等效代码其实是Monitor类的Enter和Exit两个方法 private object locker = new object(); public void Work() { lock (locker) { // 做一些需要线程同步的工作 } } private object locker = new o

.NET基础拾遗(7)多线程开发基础1

一.多线程编程的基本概念 1.1 操作系统层面的进程和线程 (1)进程 进程代表了操作系统上运行着的一个应用程序.进程拥有自己的程序块,拥有独占的资源和数据且可以被操作系统调度. But,即使是同一个应用程序,当被强制启动多次时,也会被安放到不同的进程之中单独运行. 直观地理解进程最好的方式就是通过进程管理器浏览,其中每条记录就代表了一个活动着的进程: (2)线程 线程有时候也被称为轻量级进程,是一个可以被调度的单元且维护自己的堆栈和上下文环境. 线程是附属于进程的,一个进程可以包含1个或多个线

.NET基础拾遗(7)多线程开发基础3

一.如何使用异步模式? 异步模式是在处理流类型时经常采用的一种方式,其应用的领域相当广阔,包括读写文件.网络传输.读写数据库,甚至可以采用异步模式来做任何计算工作.相对于手动编写线程代码,异步模式是一个高效的编程模式. (1)所谓异步模式是个什么鬼?   在启动一个操作之后可以继续执行其他工作而不会发生阻塞. 以读取文件为例,在同步模式下,当程序执行到Read方法时,需要等到读取动作结束后才能继续往下执行.而异步模式则可以简单地通知开始读取任务之后,继续其他的操作. 异步模式的优点就在于不需要使

ios多线程开发基础

多线程编程:下载数据时,开辟子线程,减少阻塞时间,和主线程并发运行,提升用户体验 1.Thread 1>新建Thread对象,带一selector方法,调用start方法,开启子线程 2>thread初始化自带的selector方法中请求数据 3>数据请回来后,调用self的performSelectorOnMainThread方法,又带一selector,以及相应的参数,通常是请求回的数据,如果不是self,将找不到selector方法//必须是self 4>对请求回的数据进行处

Daydream VR入门基础教程,VR开发基础知识——VR view基本介绍

VR view基本介绍 VR view是Google在2016年4月推出的一个VR基本概念,是一种"客户端"VR显示技术,可将 360 度照片或视频部署在各种设备上的简易方式,囊括 PC 端和移动端.在APP上嵌入VR View代码之后,全景照片或者视频将会变成分屏的VR内容.用户可以在手机上通过Cardboard等盒子或者在PC端通过头盔体验VR. 我们来看看一张VR全景图片是什么样的: 为什么是两张一模一样的呢?因为这是分别给左右眼观看的,遵循了VR view视图的基本规则,关于V

.NET基础拾遗(7)Web Service的开发与应用基础

Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基础 (7)WebService的开发与应用基础 一.SOAP和Web Service的基本概念 Web Service基于SOAP协议,而SOAP本身符合XML语法规范.虽然.NET为Web Service提供了强大的支持,但了解其基本机制对于程序员来说仍然是必需的. 1.1 神马是SOAP协议?

基础拾遗------redis详解

基础拾遗 基础拾遗------redis详解 基础拾遗------反射详解 基础拾遗------委托详解 基础拾遗------接口详解 基础拾遗------泛型详解 前言 这篇文章和以往的基础拾遗有所不同,以前的介绍的都是c#基础,今天介绍的是redis.因为项目中一只在使用,我想现在大部分项目中都会用到nosql,缓存,今天就介绍一下redis..废话少说下面开始正题. 1.redis是什么? Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. 对的redi

项目管理师备考知识点精讲之信息系统开发基础考情分析

信息系统项目管理师考试是计算机软件水平考试中的一个高级资格考试,是软考中的大热门.信息系统项目管理师证书含金量高,可以评高级职称,是申请高级项目经理的必要条件,还可以挂靠.下面希赛软考学院为您带来信息系统项目管理师备考知识点集锦之信息系统开发基础考情分析,专业老师根据历年真题分析总结的重点内容,让您备考期间少走弯路,高效学习,顺利通过考试.  信息系统开发基础考情分析 根据对历年的考试真题进行分析,本章要求考生掌握以下几个方面的知识: (1)信息与信息系统:信息系统的概念.信息系统的功能.信息系