对 C# 开发者来说,不可不理解清楚 Thread、ThreadPool 和 Task 这三个概念。这也是面试频率很高的话题,在 StackOverflow 可以找到有很多不错的回答,我总结整理了一下。

Thread

Thread 是一个实际的操作系统级别的线程(OS 线程),有自己的栈和内核资源。Thread 允许最高程度的控制,你可以 Abort、Suspend 或 Resume 一个线程,你还可以监听它的状态,设置它的堆栈大小和 Culture 等属性。Thread 的开销成本很高,你的每一个线程都会为它的堆栈消耗相对较多的内存,并且在线程之间的处理器上下文切换时会增加额外的 CPU 开销。

ThreadPool

ThreadPool(线程池)是一堆线程的包装器,由 CLR 维护。你对线程池中的线程没有任何控制权,你甚至无法知道线程池什么时候开始执行你提交的任务,你只能控制线程池的大小。简单来说,线程池调用线程的机制是,它首先调用已创建的空闲线程来执行你的任务,如果当前没有空闲线程,可能会创建新线程,也可能会等待。

使用 ThreadPool 可以避免创建太多线程的开销。但是,如果你向 ThreadPool 提交了太多长时间运行的任务,它可能会被填满,这时你提交的后面的任务可能最终会等待前面的长时间运行的任务执行完成。此外,线程池没有提供任何方法来检测一个工作任务何时完成(不像 Thread.Join()),也没有方法来获取结果。因此,ThreadPool 最好用于调用者不需要结果的短时操作。

Task

Task 是 TPL(Task Parallel Library)提供一个类,它在 Thread 和 TheadPool 之间提供了两全其美的解决方案。和 ThreadPool 一样,Task 并不创建自己的OS 线程。相反,Task 是由 TaskScheduler 调度器执行的,默认的调度器只是在 ThreadPool 上运行。

与 ThreadPool 不同的是,Task 还允许你知道它完成的时间,并获取返回一个结果。你可以在现有的 Task 上调用 ContinueWith(),使它在任务完成后运行更多的代码(如果它已经完成,就会立即运行回调)。

你也可以通过调用 Wait() 来同步等待一个任务的完成(或者,通过获取它的 Result 属性)。与 Thread.Join() 一样,这将阻塞调用线程,直到任务完成。通常不建议同步等待任务执行完成,它使调用线程无法进行任何其他工作。如果当前线程要等待其它线程任务执行完成,建议使用 async/await 异步等待,这样当前线程可以空闲出来去处理其它任务,比如在 await Task.Delay() 时,并不占用线程资源。

由于任务仍然在 ThreadPool 上运行,因此不应该将其用于长时任务的执行,因为它们会填满线程池并阻塞新的工作任务。相反,Task 提供了一个 LongRunning 选项,它将告诉 TaskScheduler 启用一个新的线程,而不是在 ThreadPool 上运行。

所有较新的上层多线程 API,包括 Parallel.ForEach()、PLINQ、async/await 等,都是建立在 Task 上的。

Thread 和 Task 简单示例

下面通过一个简单示例演示 Thread 和 Task 的使用,注意他们是如何创建、传参、执行和等待执行完成的。

static void Main(string[] args)
{
// 创建两个新的 Thread
var thread1 = new Thread(new ThreadStart(() => PerformAction("Thread", 1)));
var thread2 = new Thread(new ThreadStart(() => PerformAction("Thread", 2))); // 开始执行线程任务
thread1.Start();
thread2.Start(); // 等待两个线程执行完成
thread1.Join();
thread2.Join(); Console.WriteLine("Theads done!"); Console.WriteLine("===我是分隔线==="); // 创建两个新的 Task
var task1 = Task.Run(() => PerformAction("Task", 1));
var task2 = Task.Run(() => PerformAction("Task", 2)); // 执行并等待两个 Task 执行完成
Task.WaitAll(new[] { task1, task2 }); Console.WriteLine("Tasks done!"); Console.ReadKey();
} static void PerformAction(string threadOrTask, int id)
{
var rnd = new Random(id);
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{threadOrTask}: {id}: {i}", id, i);
Thread.Sleep(rnd.Next(0, 1000));
}
}

运行效果:

注意到,相比之下 Task 比 Thread 好用得多,加上前文 Task 和 Thread 的对比,对我们编码的指导意义是:大多数情况我们应该使用 Task,而不要直接使用 Thread,除非你明确知道你需要一个独立的线程来执行一个长耗时的任务。

Thread、ThreadPool 和 Task的更多相关文章

  1. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

  2. .NET 异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消

    今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...

  3. Thread,ThreadPool,Task

    线程分为前台和后台.比如我们直接new一个Thread这就是前台线程. 前台线程一定会执行. 比如我们创建2个线程:1号,2号,同时执行,假设1号是主线程,1执行完了,依旧会等待2执行完成,整个程序才 ...

  4. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  5. C# Thread、ThreadPool、Task、Invoke、BeginInvoke、async、await 汇总

    本文将主要通过"同步调用"."异步调用"."异步回调"三个示例来讲解在用委托执行同一个"加法类"的时候的的区别和利弊. ...

  6. Thread,ThreadPool,Task, 到async await 的基本使用方法和理解

    很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧. ...

  7. 异步多线程 Thread ThreadPool Task

    一.线程 Thread ThreadPool 线程是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以 ...

  8. NET 异步多线程,THREAD,THREADPOOL,TASK,PARALLEL

    .NET 异步多线程,THREAD,THREADPOOL,TASK,PARALLEL,异常处理,线程取消 今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主 ...

  9. .NET异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消

    今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...

  10. 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource

    1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...

随机推荐

  1. java继承子类实例化过程(细节解释)

    1 package face_08; 2 class Fu{ 3 Fu(){ 4 super(); 5 show(); 6 return; 7 } 8 void show() { 9 System.o ...

  2. Redis 源码简洁剖析 07 - main 函数启动

    前言 问题 阶段 1:基本初始化 阶段 2:检查哨兵模式,执行 RDB 或 AOF 检测 阶段 3:运行参数解析 阶段 4:初始化 server 资源管理 初始化数据库 创建事件驱动框架 阶段 5:执 ...

  3. tcp 中 FLAGS字段,几个标识:SYN, FIN, ACK, PSH, RST, URG.

    在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG. 其中,对于我们日常的分析有用的就是前面的五个字段.它们的含义是: 1.SYN表示建立 ...

  4. 截取一段时间内的log日志

    可以使用sed命令对log文件进行抽取操作:1,sed查看某时间段到现在的系统日志:sed -n '/May 20 17/,$p' /var/log/messages | less2,sed 截选时间 ...

  5. Python property动态属性

    from datetime import datetime, date class User: def __init__(self, name, birthday): self.name = name ...

  6. redis集群升级,数据迁移及校验

    本次由于安全漏洞原因,需要降redis3升级为redis6,涉及到数据迁移及校验等,用阿里redis-shake迁移工具迁移,并用阿里RedisFullCheck工具进行数据比对 一.新redis安装 ...

  7. 如何按规定的格式向mysql中导入数据

    1.首先我们拿到数据,数据必须按照一定的格式书写的.如用|区分字段,换行区分row 12107 | 心情1 | 今天的心情很不好啊. 12108 | 天气 | 今天天气还行. 12109 | 臭美 | ...

  8. 微信小程序开发常用功能

    获取用户信息 调用 wx.getUserProfile 方法获取用户基本信息.页面产生点击事件(例如 button 上 bindtap 的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回 ...

  9. 关于Miller-Rabin与Pollard-Rho算法的理解(素性测试与质因数分解)

    前置 费马小定理(即若P为质数,则\(A^P\equiv A \pmod{P}\)). 欧几里得算法(GCD). 快速幂,龟速乘. 素性测试 引入 素性测试是OI中一个十分重要的事,在数学毒瘤题中有着 ...

  10. 编译安装基于fastcgi模式的多虚拟主机的wordpress和discuz的LAMP架构

    一.环境准备 两台主机: httpd+php(fastcgi模式) mariadb 服务器 软件版本: mariadb-10.2.40-linux-x86_64.tar.gz apr-1.7.0.ta ...