Thread、ThreadPool、Task之间的联系

news/2024/11/18 15:38:55/文章来源:https://www.cnblogs.com/xixi-in-summer/p/18334914

1、ThreadPool线程池

一个应用程序最多只能有一个线程池。线程池是一种多线程处理形式,通过QueueUserWorkItem()将任务添加到队列中,然后创建线程(后台线程,又称工作者线程)自动启动这些任务来处理。其中,最小线程数即核心线程数(corePoolSize)是线程池中长期保持的线程数,即使它们处于闲置状态,也不会被终止。普通线程空闲一段时间后,它们会被回收,以减少资源占用。

 

static void Main(string[] args){//获取默认线程池允许开辟的最大工作线程数和最大I/O异步线程数ThreadPool.GetMaxThreads(out int maxWorkThreadCount, out int maxIOThreadCount);Console.WriteLine($"maxWorkThreadCount:{maxWorkThreadCount},maxIOThreadCount:{maxIOThreadCount}");//获取默认线程池并发工作线程数和I/O异步线程数ThreadPool.GetMinThreads(out int minWorkThreadCount, out int minIOThreadCount);           Console.WriteLine($"minWorkThreadCount:{minWorkThreadCount},minIOThreadCount:{minIOThreadCount}");//一般不为线程池中的线程数设置上限,因为将会导致死锁。//假设请求队列中所有的工作项全都因等待第1001个工作项发出信号而阻塞,如果设置了最大1000个线程,那所有线程都将阻塞var success = ThreadPool.SetMaxThreads(8, 8);//只能设置>=最小并发工作线程数和I/O线程数for (int i = 0; i < 20; i++){ThreadPool.QueueUserWorkItem(s =>{var workThreadId = Thread.CurrentThread.ManagedThreadId;var isBackground = Thread.CurrentThread.IsBackground;var isThreadPool = Thread.CurrentThread.IsThreadPoolThread;Console.WriteLine($"work is on thread {workThreadId}, Now time:{DateTime.Now.ToString("ss.ff")},"+ $" isBackground:{isBackground}, isThreadPool:{isThreadPool}");Thread.Sleep(5000);//模拟工作线程运行});}Console.ReadLine();}

运行结果:

适用场景:

  • 不要将长时间运行的操作放进线程池中,适合任务量大且短时的场景,长时任务用Thread。
  • 不应该阻塞线程池中的线程;
  • 线程池最多管理线程数量=“处理器数 * 250”,最小不小于处理器数。

优点:

  • 线程池能够有效地管理和复用线程资源,减少创建和销毁线程的开销,从而提高应用程序的性能和资源管理效率。

缺点:

  • ThreadPool原生不支持对工作线程启动、取消、完成、失败通知等交互性操作,同样不支持获取函数返回值,灵活度不够,Thread原生有Abort (同样不推荐)、Join等可选择。

 

2、Thread

在System.Threading 命名空间下,包含了用于创建和控制线程的Thread 类。对线程的常用操作有:启动线程、终止线程、合并线程和让线程休眠等。

Thread默认是前台线程。应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。

2.1、终止线程Abort

两种终止线程的方法:

  • 一种是事先设置一个布尔变量,在其他线程中通过修改该变量的值作为传递给该线程是否需要终止的判断条件,而在该线程中循环判断该条件,以确定是否退出线程,这是结束线程的比较好的方法,实际编程中一般使用这种方法。
  • 第二种方法是调用t.Abort() 方法强行终止线程,Abort 方法没有任何参数,线程一旦被终止,就无法再重新启动。由于Abort 通过抛出异常强行终止结束线程,因此在实际编程中,应该尽量避免采用这种方法。

延申:

  调用Abort 方法终止线程时,公共语言运行库(CLR)会引发ThreadAbortException 异常,程序员可以在线程中捕获ThreadAbortException 异常,然后在异常处理的Catch 块或者Finally块中作释放资源等代码处理工作;但是,线程中也可以不捕获ThreadAbortException 异常,而由系统自动进行释放资源等处理工作。
  注意,如果线程中捕获了ThreadAbortException 异常,系统在finally 子句的结尾处会再次引发ThreadAbortException 异常,如果没有finally 子句,则会在Catch 子句的结尾处再次引发该异常。为了避免再次引发异常,可以在finally 子句的结尾处或者Catch 子句的结尾处调用System.Threading.Thread.ResetAbort 方法防止系统再次引发该异常。
  使用Abort 方法终止线程,调用Abort 方法后,线程不一定会立即结束。这是因为系统在结束线程前要进行代码清理等工作,这种机制可以使线程的终止比较安全,但清理代码需要一定的时间,而我们并不知道这个工作将需要多长时间。因此,调用了线程的Abort 方法后,如果系统自动清理代码的工作没有结束,可能会出现类似死机一样的假象。为了解决这个问题,可以在主线程中调用子线程对象的Join 方法,并在Join 方法中指定主线程等待子线程结束的等待时间。

2.2、合并线程Join

用于把两个并行执行的线程合并为一个单个的线程。

如果一个线程t1 在执行的过程中需要等待另一个线程t2 结束后才能继续执行,可以在t1 的程序模块中调用t2 的join()方法。

例如:t2.Join();这样t1 在执行到t2.Join()语句后就会处于阻塞状态,直到t2 结束后才会继续执行。

但是假如t2 一直不结束,那么等待就没有意义了。为了解决这个问题,可以在调用t2 的Join 方法的时候指定一个等待时间。

例如:t2.Join(100);将t2 合并到t1 后,t1 只等待100 毫秒,然后不论t2 是否结束,t1 都继续执行。

Join 方法通常和Abort 一起使用。由于调用某个线程的Abort 方法后,我们无法确定系统清理代码的工作什么时候才能结束,因此如果希望主线程调用了子线程的Abort 方法后,主线程不必一直等待,可以调用子线程的Join 方法将子线程连接到主线程中,并在连接方法中指定一个最大等待时间,这样就能使主线程继续执行了。

2.3、线程优先级

当线程之间争夺CPU 时间片时,CPU 是按照线程的优先级进行服务的。在C#应用程序中,可以对线程设定五个不同的优先级,由高到低分别是Highest、AboveNormal、Normal、BelowNormal 和Lowest。在创建线程时如果不指定其优先级,则系统默认为Normal。

Thread t=new Thread(new ThreadStart(enterpoint));
t.priority=ThreadPriority.AboveNormal;

通过设置线程的优先级可以改变线程的执行顺序,所设置的优先级仅仅适用于这些线程所属的进程。
注意:当把某线程的优先级设置为Highest 时,系统上正在运行的其他线程都会终止,所以使用这个优先级别时要特别小心。

 

3、Task

Task是.NET Framework 4.5加入的概念,之前实现多线程是利用Thread类,在实际编码中基本用Task,因为它比Thread更易理解,更易运用,更安全可靠。

Task是基于线程池封装实现的,解决了线程池无法挂起中止线程等这些问题。而且Task的性能优于ThreadPool因为它使用的不是线程池的全局队列,而是使用的是本地队列。使得线程之间竞争资源的情况减少。Task提供了丰富的API,开发者可对Task进行多种管理。

Task和Thread差异:

  • task与thread对比,task相当于应用层,thread更底层,但二者是不一样的,没有隶属关系
  • task是在线程池上创建,是后台线程(主线程不会等其完成);Thread是单个线程,默认是前台线程
  • task可以直接获取返回值,thread不能直接从方法返回结果(可以使用变量来获取结果)
  • 使用task.ContinueWith()方法可以继续执行其他任务。Thread无连续性,当线程完成工作时,不能告诉线程开始其他操作。 尽管可以使用Join()等待线程完成,但是这会阻塞主线程
  • task借助CancellationTokeSource类可以支持任务中的取消,当thread处于运行中时,无法取消它
  • task能方便捕捉到运行中的异常,thread在父方法中无法捕捉到异常

task的使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/779817.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基准测试基础

基准测试 定义 基准测试是一种用于衡量计算机系统,软件应用或硬件组件性能的测试方法。 基准测试旨在通过运行一系列标准化的任务场景来测量系统的性能表现,从而帮助评估系统的各种指标,如响应时间、并发用户数、TPS、资源利用率、交易成功率等。 特质 ① 可重复性: 可进行重…

智林 - AI 答题应用平台介绍

智林 - AI 答题应用平台 一、项目介绍 智林AI答题是一款基于 Vue 3 + Spring Boot + Redis + ChatGLM + RxJava + SSE 的 AI 答题应用平台。 用户可以基于 AI 快速制作并发布答题应用,支持检索、分享、在线答题并基于 AI 得到回答总结;管理员可以集中管理和审核应用。 什么是…

博客园自定义皮肤工具推荐:awescnb

简介 awescnb是一个用于博客园(Cnblogs)的自定义皮肤和功能增强插件。它允许用户通过简单的配置来自定义其博客的外观和增加一些额外的功能。下面是对awescnb的简要介绍: 功能特点: 自定义皮肤:用户可以选择不同的皮肤主题,包括背景颜色、字体样式等,以个性化他们的博客…

外部存储器

磁盘存储器磁盘存储器的性能指标磁盘地址磁盘阵列固态硬盘SSD

Ubuntu22.04上安装esp-idf

一、安装准备建议使用Ubuntu 20.04 或 Ubuntu 22.04 操作系统为了在 ESP32-C3 中使用 ESP-IDF,需要安装一些依赖包 sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0…

数据结构 分块 莫队

数据结构 分块 & 莫队分块 一种优化暴力的思想。 通常是将原数据划分成适当块(一般为 \(\sqrt{n}\)),对每块数据进行预处理,进而达到比暴力更优的时间复杂度。 划分 确定块长后,一般需要开两个数组存储每一块的右边界与原数据所属块序号,更加方便后续操作。 int sq=s…

改写socket编程并解释socket通信原理

如果你仔细看我之前的博客,会看到那个手机打电话的示例,但是那段代码彻底写死了,真正编程的时候一定要写活了,不信你看看很多的程序的配置文件就是这样的,为什么单独分离个配置文件出来,就是为了便于修改配置,这就是把程序写活的最好的例子。 言归正传,直接上代码。 服…

工程项目综合管理系统解析:哪一款适合你的企业?

国内外主流的10款工程项目综合管理系统对比:PingCode、Worktile、广联达、明源云、中望软件、Oracle Primavera、Asana、Wrike、Zoho Projects、Basecamp。在处理复杂的工程项目时,选择合适的综合管理系统可能是一个令人头痛的问题。项目延误、成本超支和资源管理不当等问题常…

预训练语言模型去偏方法——与特定任务相关

一、对抗学习 1.1 ADV-标准对抗  主要思想:防止鉴别器识别受保护的属性。以对抗性方法训练模型,并明确掩盖受保护信息。 损失函数:1.2 EADV-优化对抗ADV存在的问题:在某些情况下,即使对抗性组件似乎做得很完美,仍有相当数量的受保护信息,并且可以从编码的表示中提取。…

使用wx制作一个桌面软件

前面因为抓取数据,为了方便期间做了各界面,用到了wx,觉得很好用,所以最近几天专门看了一下wx的使用,并练习了一下。 代码:import os,sys,re,time import wx,wx.xrc,wx.adv,wx.grid import json,math,random import subprocess,threading from win32api import GetSystemM…