news 2026/7/18 1:03:33

.NET进阶——深入理解线程(1)同步异步与单线程多线程的区分

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET进阶——深入理解线程(1)同步异步与单线程多线程的区分

今天我们来详细介绍一下多线程,多线程是.NET中非常重要的知识点,需要完全掌握。

一、什么是多线程?

在了解线程之前,我们需要知道什么是进程,所谓进程,就是指操作系统中运行的程序,比如我们自己写的在运行的.NET程序,或者是正在运行的QQ、微信、浏览器等,这些都叫做一个进程,进程与进程之间保持独立,比如浏览器崩了不会影响微信。

如果操作系统是一个工厂,那么进程就是一个车间,而车间里的工人就是线程。如果一个进程里只有一个工人,那就是单线程,由于只有一个工人,所以效率比较慢;如果一个进程里有多个工人,那就是多线程,由于可以多个工人相互配合,所以效率相对较高。

二、什么是同步异步?

这里需要重点介绍一下同步异步的知识,因为总有小伙伴会将同步异步单线程多线程搞混。

  • 同步:你自己点外卖,拿着手机一直等:等商家接单、等商家做、等骑手送,期间你啥也干不了,就傻坐着等(这就是「线程阻塞」)。

👉 不管是 1 个人干(单线程)还是 2 个人干(多线程),只要你等事儿干完,就是同步。

  • 异步:你用 APP 点完外卖,把手机一扔,该刷剧刷剧、该打游戏打游戏,等外卖到了(手机响通知)你再去拿 ——不等事儿干完,先干别的

👉 这就是异步,重点是「不傻等」,和 “几个人干活” 没关系。

三、单线程多线程与同步异步的区别

  1. 同步 / 异步:只看你「要不要等事儿干完」(做事的方式);
  2. 单线程 / 多线程:只看你「有几个人干活」(干活的人)。

3.1 区分同步异步

如何区分同步异步,只要看当前运行的线程会不会等待方法执行结束,比如我下面这两个例子:

namespaceConsoleTest{publicclassProgram{staticvoidMain(string[]args){{Console.WriteLine("同步点外卖");SyncOrder();}Console.WriteLine("============================================");{Console.WriteLine("异步点外卖");AsyncOrder();}}staticvoidSyncOrder(){Console.WriteLine("下单成功");Console.WriteLine("一直盯着手机等待骑手送达");Thread.Sleep(2000);Console.WriteLine("两秒钟后骑手送到");}staticvoidAsyncOrder(){Console.WriteLine("下单成功");Threadthread=newThread(()=>{Console.WriteLine("让手机自己等待,外面到了再提醒我");Thread.Sleep(2000);Console.WriteLine("手机提示外卖到了");});thread.Start();Console.WriteLine("我自己去洗碗了~~~~");}}}

Thread.Sleep的作用是让当前正在执行的线程暂停(休眠)指定的毫秒数,期间释放 CPU 资源(让给其他线程干活),休眠时间到了再继续执行。我们返回刚刚的概念,如何区分同步异步,只要看当前运行的线程会不会等待方法执行结束,由于SyncOrder中,我让当前运行的线程睡眠了2秒,这个线程必须等待2秒,2秒后才可以继续运行其他代码,所以这里的SyncOrder是同步方法。

不同的是,当主线程开始执行AsyncOrder后,AsyncOrder会在内部新创建一个线程,这个新线程负责等待2秒,但是需要注意的是:主线程并不会等待新线程执行完毕,当方法内部执行了thread.Start()后,新线程开始执行等待方法,而主线程继续往下走,执行Console.WriteLine("我自己去洗碗了~~~~")这个洗碗操作,所以这里是异步方法。

执行结果:

同步点外卖 下单成功 一直盯着手机等待骑手送达 两秒钟后骑手送到============================================异步点外卖 下单成功 我自己去洗碗了~~~~让手机自己等待,外面到了再提醒我 手机提示外卖到了

那这里的异步方法能不能也改成同步方法呢?答案是可以的,同步方法的核心不就是让主线程等待嘛,那我们也只需要让主线程等待新线程执行,不就把它变成同步方法了:

staticvoidAsyncOrder(){Console.WriteLine("下单成功");Threadthread=newThread(()=>{Console.WriteLine("让手机自己等待,外面到了再提醒我");Thread.Sleep(2000);Console.WriteLine("手机提示外卖到了");});thread.Start();// 让主线程等待新线程执行完毕thread.Join();Console.WriteLine("我自己去洗碗了~~~~");}

我们这里只加了一行代码:thread.Join();作用就是让主线程等待新线程执行完毕,随后再去执行下面的洗碗方法,这样改变后,这个AsyncOrder也就变成了一个同步方法。
修改后的结果:

同步点外卖 下单成功 一直盯着手机等待骑手送达 两秒钟后骑手送到============================================异步点外卖 下单成功 让手机自己等待,外面到了再提醒我 手机提示外卖到了 我自己去洗碗了~~~~

原本下单成功就可以去洗碗,现在要等待外卖到了再去洗碗,可见AsyncOrder也变成了一个同步方法。虽然都是让主线程陷入等待,但是两个同步方法实现的等待逻辑有所不同:

  • 一开始的同步方法只有一个主线程,主线程会等待是因为执行了Thread.Sleep方法,相当于让它自己陷入了睡眠。
  • 后面由异步方法改造而成的同步方法,主线程会等待是因为执行力thread.Join();这个方法,导致主线程必须等待新线程执行完毕,相当于必须等待别人完成任务,你才可以开始。

新手小伙伴一定要好好看这两个代码,才能深入理解同步异步的区别!!!!

3.2 区分单线程多线程

相比之下区分单线程多线程就容易得多,只要看代码是否运行在多个线程上。换句话说,也就是看也没有创建新的线程并且启动新的线程。

由于下面的代码中,我并没有创建新的线程,更没有调用,所以这个代码在主线程上运行:

namespaceSingleMultiThreadDemo{classProgram{staticvoidMain(string[]args){// 单线程核心:所有代码都在主线程(线程ID固定)Console.WriteLine($"主线程代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");// 调用普通方法,还是在主线程DoSomething();// 即使有循环/延时,依然是同一个线程Thread.Sleep(1000);Console.WriteLine($"延时后代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");}staticvoidDoSomething(){Console.WriteLine($"普通方法代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");}}}

调用结果

主线程代码 —— 线程ID:1 普通方法代码 —— 线程ID:1 延时后代码 —— 线程ID:1

下面的代码中,我创建了新的线程,并且启动了新的线程,所以这个代码在多个线程上运行:

namespaceSingleMultiThreadDemo{classProgram{staticvoidMain(string[]args){Console.WriteLine($"主线程代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");// 多线程核心:创建新线程,新线程有独立IDThreadnewThread=newThread(()=>{Console.WriteLine($"新线程代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");});newThread.Start();// 线程池/Task.Run也是多线程(常用)System.Threading.Tasks.Task.Run(()=>{Console.WriteLine($"线程池线程代码 —— 线程ID:{Thread.CurrentThread.ManagedThreadId}");});// 等一下,避免程序提前退出Thread.Sleep(1000);}}}

执行结果:

主线程代码 —— 线程ID:1新线程代码 —— 线程ID:7线程池线程代码 —— 线程ID:6

多线程这里需要注意的是,如果创建了其他线程,但是没有启动,那么程序还是运行在主线程,所以还是单线程的。

3.3 异步与多线程

需要重点注意的是,异步不一定代表多线程,多线程只是实现异步的其中一种方法,单线程也可以实现异步,只要保障不让程序处于等待状态就行了,比如下面这个方法,你不需要能每行代码都理解,但是你需要理解异步不只可以由多线程实现这个思想

namespaceAsyncNotMultiThreadDemo{classProgram{staticasyncTaskMain(string[]args){Console.WriteLine("===== 单线程异步(无多线程) =====");// 打印主线程ID(全程唯一)Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}");Console.WriteLine("1. 点击下载歌曲,手机开始处理下载请求");// Task.Delay:异步等待(无新线程,靠操作系统定时器)vardownloadTask=Task.Delay(2000);// 模拟下载耗时2秒// 异步核心:不等下载完成,直接干别的(刷歌单)Console.WriteLine("2. 不等下载完成,继续刷歌单(异步)");Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}");// 还是主线程// 等下载完成(全程无新线程)awaitdownloadTask;Console.WriteLine("3. 下载完成!当前线程ID:{Thread.CurrentThread.ManagedThreadId}");}}}

Task.Delay()Thread.Sleep()不同,Task.Delay靠操作系统定时器实现,不阻塞当前线程,所以能单线程异步等待,Thread.Sleep()是只能单线程同步等待,所以在这里使用Task.Delay()时,主程序并不会停下来等待,而是立即执行后面的代码,当代码执行到await downloadTask;时,才会等待计时结束。于是就实现了非多线程的异步。那我们能不能把这个方法改成同步呢?

namespaceAsyncNotMultiThreadDemo{classProgram{staticasyncTaskMain(string[]args){Console.WriteLine("===== 单线程异步(无多线程) =====");// 打印主线程ID(全程唯一)Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}");Console.WriteLine("1. 点击下载歌曲,手机开始处理下载请求");// Task.Delay:异步等待(无新线程,靠操作系统定时器)vardownloadTask=Task.Delay(2000);// 模拟下载耗时2秒// 等下载完成(全程无新线程)awaitdownloadTask;// 异步核心:不等下载完成,直接干别的(刷歌单)Console.WriteLine("2. 不等下载完成,继续刷歌单(异步)");Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}");// 还是主线程Console.WriteLine("3. 下载完成!当前线程ID:{Thread.CurrentThread.ManagedThreadId}");}}}

当我们把await downloadTask;这个等待指令放到执行前面后,主线程就无法往下执行代码,而是会卡在这里,于是这个就又变成了同步。

然鹅,我们实现异步最常用的还是多线程,我们再举一个例子:

namespaceAsyncNotMultiThreadDemo{classProgram{staticvoidMain(string[]args){Console.WriteLine("===== 多线程异步 =====");Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");Console.WriteLine("1. 喊室友帮我下载歌曲");// 新建线程(多线程)实现异步ThreadnewThread=newThread(()=>{Console.WriteLine($"室友线程ID:{Thread.CurrentThread.ManagedThreadId}");Thread.Sleep(2000);// 模拟下载2秒Console.WriteLine("2. 室友:下载完成!");});newThread.Start();// 异步核心:不等室友,自己刷歌单Console.WriteLine("3. 不等室友,我继续刷歌单(异步)");Thread.Sleep(3000);// 等室友完成,避免程序提前退出}}}

这个代码中,我们并不关心是由线程是什么时候结束的,我们用Thread.Sleep(3000)在主线程等待了一个很长的时间来确保它一定可以结束。

=====多线程异步=====主线程ID:11.喊室友帮我下载歌曲3.不等室友,我继续刷歌单(异步) 室友线程ID:72.室友:下载完成!

但是,如果规定了室友线程什么时候结束呢?

namespaceAsyncNotMultiThreadDemo{classProgram{staticvoidMain(string[]args){Console.WriteLine("===== 多线程同步 =====");Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");Console.WriteLine("1. 喊室友帮我下载歌曲");// 新建线程(多线程)实现异步ThreadnewThread=newThread(()=>{Console.WriteLine($"室友线程ID:{Thread.CurrentThread.ManagedThreadId}");Thread.Sleep(2000);// 模拟下载2秒Console.WriteLine("2. 室友:下载完成!");});newThread.Start();newThread.Join();// 异步核心:不等室友,自己刷歌单Console.WriteLine("3. 等室友,室友执行结束我才可以刷歌单(同步)");}}}

现在,我只是在调用新的线程后加了一行代码:newThread.Join(),这个代码告诉主线程,你必须在这里等待室友线程执行结束,你才可以继续往下执行,于是就变成了同步,这里的同步不是单线程同步,而是主线程同步等待其他线程执行完成。

执行结果

=====多线程同步=====主线程ID:11.喊室友帮我下载歌曲 室友线程ID:72.室友:下载完成!3.等室友,室友执行结束我才可以刷歌单(同步)

总结:

  • 不管是何种类型的同步异步,我们只要关心主线程是否会等待,换句话说,我们只要关心主线程是否会阻塞。
  • 不管是何种类型的单线程多线程,我们只要关心程序是运行在几个线程上的,即有没有创建新的线程并且启动新的线程。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 13:15:51

重构智慧书-第18条:实力与实干

一、原文呈现实力与实干要得声名显赫,必须兼有实力与实干精神。有了这两者,显赫的声名就会如虎添翼。有实干精神的平庸之辈比无实千精神的高明之辈更有成就。实干创实绩。肯出大力者必得大名。有的人连最简单的事情也不肯下力去干。干与不干,差不多总与一…

作者头像 李华
网站建设 2026/7/16 21:15:37

读捍卫隐私08智能出行

1. 互联网电话 1.1. 和打印机一样,VoIP电话的系统更新也可以被伪造和接受 1.2. 大多数VoIP电话都有一个免提选项,让你可以在你的隔间或办公室中将某人的通话用扬声器播放 1.2.1. 意味着在电话听筒之外不仅有一个扬声器,还有一个麦克风 1.…

作者头像 李华
网站建设 2026/7/18 8:16:05

媒体专访实录:LobeChat创始人谈开源初心

LobeChat 技术架构深度解析:从容器化部署到插件生态的全栈实践 在大模型浪潮席卷全球的今天,AI 对话系统早已不再是实验室里的概念玩具。从客服机器人到个人助手,自然语言交互正成为数字世界的新入口。然而,当 OpenAI 的 ChatGPT …

作者头像 李华
网站建设 2026/7/14 13:17:04

小喵播放器 1.1.2| 一款支持视频超分的播放器,支持网页视频以及B站番剧

小喵播放器是一款基于mpv播放器内核的本地视频播放器,专为动漫观看场景进行优化。它集成了Anime4K实时超分辨率算法,在播放过程中对低分辨率动画进行画质增强。支持常见视频格式、字幕与音轨切换、播放记录、截图等基础播放器功能,并通过GLSL…

作者头像 李华
网站建设 2026/7/14 22:36:48

LobeChat待办事项提取与提醒功能实现

LobeChat待办事项提取与提醒功能实现 在日常使用聊天工具时,我们常常会随口说一句:“记得提醒我明天开会”或者“下周要交报告”。这些话听起来像是对 AI 的一次普通请求,但如果说完就忘,那它不过是一段被淹没在对话流中的信息碎…

作者头像 李华
网站建设 2026/7/14 22:36:47

LobeChat会话管理机制揭秘:让每一次对话都井然有序

LobeChat会话管理机制揭秘:让每一次对话都井然有序 在今天,AI 对话不再是简单的“你问我答”,而是逐渐演变为一种持续、多线程、跨场景的认知协作。无论是开发者调试模型输出,产品经理反复打磨提示词,还是研究人员横向…

作者头像 李华