C#多线程编程(二)线程池与TPL
一、直接使用线程的问题
- 每次都要创建Thread对象,并向操作系统申请创建一个线程,这是需要耗费CPU时间和内存资源的。
- 无法直接获取线程函数返回值
- 无法直接捕捉线程函数内发生的异常
使用线程池可以解决第一个问题
二、.NET中的线程池
在这里只简单的介绍一下ThreadPool,由于TPL的存在,我工作中大部分使用的是TPL中的类,这是后面介绍的重点。
1. ThreadPool.QueueUserWorkItem
这个方法有三个重载
- public static bool QueueUserWorkItem(WaitCallback callBack)
- public static bool QueueUserWorkItem(WaitCallback callBack, object? state)
- public static bool QueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
第一个函数需要传入一个WaitCallback 委托,该委托的定义如下
- public delegate void WaitCallback(object? state);
第二个函数多了一个state参数,表示需要传给委托的参数,若无需传参调用第一个函数即可。
第三个函数是一个泛型版本,还多了一个布尔类型的参数preferLocal,这个参数表示传入的委托将会在放入线程池工作线程的本地队列还是线程池的全局队列。
线程池内部有本地队列和全局队列的概念,线程池遵循生产者-消费者模式,线程池还可以为线程数量提供良好的伸缩性,有关.NET线程池的详细信息,请参见https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?view=netcore-3.1
需要注意的是,线程池中的线程默认为后台线程,这意味着如下代码一般不能按预期工作。
1 static void Main(string[] args)
2 {
3 ThreadPool.QueueUserWorkItem(state =>
4 {
5 Thread.Sleep(100);
6 Console.WriteLine("执行完毕");
7 });
8 }
并且由于线程会被复用,所以不能作依赖于某个特定线程的操作。
2. 何时不使用线程池(摘录微软文档)
- 需要一个前台线程。
- 需要具有特定优先级的线程。
- 拥有会导致线程长时间阻塞的任务。 线程池具有最大线程数,因此大量被阻塞的线程池线程可能会阻止任务启动。
- 需将线程放入单线程单元。 所有 ThreadPool 线程均位于多线程单元中。
- 需具有与线程关联的稳定标识,或需将一个线程专用于一项任务。
三、更方便的解决方案(使用TPL)
1.处理线程池未解决的问题
线程池虽然解决了线程资源浪费的问题,但是以下两点还未解决
- 无法直接获取线程函数返回值
- 无法直接捕捉线程函数内发生的异常
在上一篇中MyTask类可以解决这两个问题,但由于内部是每个Task直接开一个线程,资源浪费的问题还是没有解决,所以我们是不是能够把两者结合呢?
2.Task初体验
无返回值无异常
1 static void Main(string[] args)
2 {
3 Task task = new Task(() =>
4 {
5 Thread.Sleep(100);
6 Console.WriteLine($"是否是线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
7 });
8 task.Start();
9 try
10 {
11 task.Wait();
12 }
13 catch(Exception e)
14 {
15 Console.WriteLine(e.Message);
16 }
17 }
输出如下:

无返回值有异常
1 static void Main(string[] args)
2 {
3 var task = new Task(() =>
4 {
5 Console.WriteLine($"是否是线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
6 var task1 = new Task(() =>
7 {
8 Console.WriteLine($"是否是线程池线程:{Thread.CurrentThread.IsThreadPoolThread}");
9 throw new Exception("延续任务发生异常");
10 },TaskCreationOptions.AttachedToParent);
11 task1.Start();
12 throw new Exception("主任务发生异常");
13 });
14
15 task.Start();
16 try
17 {
18 task.Wait();
19 }
20 catch(AggregateException ae)//Task内部包装了异常,有异常发生Wait()内部会抛出一个聚合异常
21 {
22 foreach(var e in ae.Flatten().InnerExceptions)//把阶梯式的聚合异常变为扁平的异常
23 {
24 Console.WriteLine(e.Message);
25 }
26 }
27 }

有返回值的就不演示了。可以看到,使用Task解决了开始的三种问题,但事物总是具有两面性,有优点也有缺点,Task会带来额外的内存分配,Task抽象层次过高,深入理解并使用好并非易事,在与async/await关键字配合编写异步代码时更加突出。
网上关于Task的使用例子很多,微软文档也很全,我在此这里补充一下需要注意的地方。
- 任务是托管线程上更高层次的抽象
- 任务的执行由任务调度器(TaskScheduler)决定
- 默认的任务调度器是线程池调度器,它使用线程池执行任务
- Task.Run()静态方法和Task.Start()实例方法以及默认的任务工厂Task.Factory都是使用默认的线程池任务调度器
- 因此说Task是对线程池的封装是不准确的
- 通过指定TaskCreationOptions.LongRunning枚举便可让任务在非线程池中的线程上执行,这样可以避免长期占用线程池中的线程,因为线程池是有大小的,一般线程池用来处理简单但量多的工作。
C#多线程编程(二)线程池与TPL的更多相关文章
- 温故知新-java多线程&深入理解线程池
文章目录 摘要 java中的线程 java中的线程池 线程池技术 线程池的实现原理 简述 ThreadPoolExecutor是如何运行的? 线程池运行的状态和线程数量 任务执行机制 队列缓存 Wor ...
- Python中的多线程编程,线程安全与锁(二)
在我的上篇博文Python中的多线程编程,线程安全与锁(一)中,我们熟悉了多线程编程与线程安全相关重要概念, Threading.Lock实现互斥锁的简单示例,两种死锁(迭代死锁和互相等待死锁)情况及 ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- .NET面试题解析(07)-多线程编程与线程同步
系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...
- .NET面试题解析(07)-多线程编程与线程同步 (转)
http://www.cnblogs.com/anding/p/5301754.html 系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实 ...
- 《转载》Python并发编程之线程池/进程池--concurrent.futures模块
本文转载自Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mult ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
随机推荐
- 【源码】Kafka订制协议如何处理粘拆包
前言 在上一篇随笔中,我们探讨了如何使用 Netty 处理自定义协议中的粘包和拆包问题.Netty 提供了高度封装的 API,帮助开发者轻松应对这一挑战,因此很多人都对其解决方案非常熟悉. 但如果我们 ...
- Android基础入门教程-参考资料
2)看视频 网上关于Android的视频教程有很多,这里分享下基神力荐的黑马教程吧: 黑马28期Android全套视频无加密完整版:密码:h7jz 52期不加密版:密码:zve8 当然下面这些视频学习 ...
- 深入理解第三范式(3NF):数据库设计中的重要性与实践
title: 深入理解第三范式(3NF):数据库设计中的重要性与实践 date: 2025/1/17 updated: 2025/1/17 author: cmdragon excerpt: 在数据库 ...
- 「CF1101F」Trucks and Cities
题意描述 有 \(N\) 座城市,第 \(i\) 座坐标为 \(a_i\) ,有 \(M\) 辆卡车,第 \(i\) 辆卡车要从城市 \(s_i\) 前往城市 \(e_i\) ,每单位长度耗油量为 \ ...
- 小程序之confirm-type改变键盘右下角的内容和input按钮详解
confirm-type的介绍 confirm-type 在什么时候使用呢? 如果说搜索框的时候,当用户输入完了之后,我们就需要 将confirm-type="search"的值设 ...
- CPU的指令周期
本文分享自天翼云开发者社区<CPU的指令周期>,作者:冯****怡 指令周期(Instruction Cycle) CPU中会有 存器.指令寄存器.控制器等多类单元.指令集,就是CPU中用 ...
- 1 使用ollama完成DeepSeek本地部署
1 ollama 1.1 什么是ollama ollama是一个开源的 LLM(大型语言模型)服务工具,用于简化在本地运行大语言模型,降低使用大语言模型的门槛,使得大模型的开发者.研究人员和爱好者能够 ...
- IAP升级(STM32)
IAP升级(STM32) IAP作用简述:将要升级的程序bin文件通过串口发送给STM32,STM32接收后存储到FLASH或者SRAM,用户通过事件(按键等)触发(也可延时自动触发)后将升级 文件夹 ...
- axurerp9怎么汉化:Axure RP9 中文激活安装教程
Axure RP 9是一款一款专业级快速产品原型设计工具,使用它可以让用户快速.高效创建应用软件或Web网站的线框图.流程图.原型和规格说明文档.采用了极简主义的设计,界面布局更加清爽简洁,操作也非常 ...
- docker - [06] 安装部署Tomcat
题记部分 一.官方测试镜像 官方文档给出以下命令,一般用来测试,用完即删,下载并运行镜像,退出镜像就会自动删除镜像?亲测不会自动删除 docker run -it --rm tomcat:9.0 使用 ...