一、直接使用线程的问题

  1. 每次都要创建Thread对象,并向操作系统申请创建一个线程,这是需要耗费CPU时间和内存资源的。
  2. 无法直接获取线程函数返回值
  3. 无法直接捕捉线程函数内发生的异常

使用线程池可以解决第一个问题

二、.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. 何时不使用线程池(摘录微软文档)

  1. 需要一个前台线程。
  2. 需要具有特定优先级的线程。
  3. 拥有会导致线程长时间阻塞的任务。 线程池具有最大线程数,因此大量被阻塞的线程池线程可能会阻止任务启动。
  4. 需将线程放入单线程单元。 所有 ThreadPool 线程均位于多线程单元中。
  5. 需具有与线程关联的稳定标识,或需将一个线程专用于一项任务。

三、更方便的解决方案(使用TPL)

1.处理线程池未解决的问题

线程池虽然解决了线程资源浪费的问题,但是以下两点还未解决

  1. 无法直接获取线程函数返回值
  2. 无法直接捕捉线程函数内发生的异常

在上一篇中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的更多相关文章

  1. 温故知新-java多线程&深入理解线程池

    文章目录 摘要 java中的线程 java中的线程池 线程池技术 线程池的实现原理 简述 ThreadPoolExecutor是如何运行的? 线程池运行的状态和线程数量 任务执行机制 队列缓存 Wor ...

  2. Python中的多线程编程,线程安全与锁(二)

    在我的上篇博文Python中的多线程编程,线程安全与锁(一)中,我们熟悉了多线程编程与线程安全相关重要概念, Threading.Lock实现互斥锁的简单示例,两种死锁(迭代死锁和互相等待死锁)情况及 ...

  3. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  4. .NET面试题解析(07)-多线程编程与线程同步

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...

  5. .NET面试题解析(07)-多线程编程与线程同步 (转)

    http://www.cnblogs.com/anding/p/5301754.html 系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实 ...

  6. 《转载》Python并发编程之线程池/进程池--concurrent.futures模块

    本文转载自Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mult ...

  7. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  9. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  10. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

随机推荐

  1. nginx平台初探-4

    模块开发高级篇(30%)   变量(80%)   综述 在Nginx中同一个请求需要在模块之间数据的传递或者说在配置文件里面使用模块动态的数据一般来说都是使用变量,比如在HTTP模块中导出了host/ ...

  2. mysql数据库指定ip远程访问(设置远程连接),赋权操作

    mysql数据库指定ip远程访问(设置远程连接) 远程访问mysql报错,ip不允许链接的情况:错误号码1045Access denied for user '用户名' @'数据库地址' (using ...

  3. LeetCode 未验证规则

    1. for循环容易忘记break,很多提交不过,都是因为这个 2. 左右指针,思考起来比较复杂,但是以代码容易理解.code精简为主,不要在乎省下一两次运算时间 3. 左右指针,有时候以left & ...

  4. Python语法使用

    由于之前学习过js,代码基本上是相同的,先看看和js有那些区别 项目 python javascript 适用版本 python3 es6,即ECMAScript 2015 运行环境 #!/usr/b ...

  5. Superset config配置文件参数

    superset配置文件config.py文件参数修改相关影响:1.如果让dashboard左侧出现筛选器,dashboard可交叉筛选,将以下三个参数设置为True "DASHBOARD_ ...

  6. ceph数据重构原理

    本文分享自天翼云开发者社区<ceph数据重构原理>,作者:x****n 在分布式存储系统Ceph中,硬盘故障是一种常见问题.为了保证数据安全,当发生硬盘故障后,分布式存储系统会依据算法对故 ...

  7. ISA-L库调研

    本文分享自天翼云开发者社区<ISA-L库调研>,作者:何****尔 1.Intel SIMD指令集 SIMD(single instruction multiple data)单指令多数据 ...

  8. Linux重要的日志文件

    1./var/log/boot.log 该文件记录了系统在引导过程中发生的事件,就是Linux系统开机自检过程显示的信息 2./var/log/syslog 只记录警告信息,常常是系统出问题的信息,所 ...

  9. QT5笔记: 32. QPainter 基本绘制

  10. Linux - Centos操作系统iso文件下载

    CENTOS VERSION DOWNLOAD LINK CentOS 8.5(2111) Download CentOS 8.4(2105) Download CentOS 8.3(2011) Do ...