C#学习(十三)——多线程与异步

news/2025/2/28 5:15:38/

一、什么是线程

程序执行的最小单元
一次页面的渲染、一次点击事件的触发、一次数据库的访问、一次登录操作都可以看作是一个一个的进程

在一个进程中同时启用多个线程并行操作,就叫做多线程
由CPU来自动处理
线程有运行、阻塞、就绪三态

代码示例:

class Program
{static void Main(string[] args){Thread thread = new Thread(() =>{Print1();});thread.Start();for(int i = 0; i < 1000; i++){Console.Write(0);}Console.Read();}static void Print1(){for (int i = 0;i < 1000; i++){Console.Write(1);}}}

运行结果为
示例代码运行结果
可以看到,在结果中,0和1的输出是交织在一起的,原因为两个线程交替着被运行,不断反复直到结束。
另外一个常用操作为Sleep();让时间暂停,使得线程进入静默状态。

二、前台线程、后台线程与线程池托管

代码举例:

class Program
{static void Main(string[] args){Thread thread = new Thread(PrintHello);thread.Start();Console.WriteLine("退出主程序");}private static void PrintHello(object? obj){while (true){Thread.Sleep(1000);Console.WriteLine("Hello from PrintHello!");}}
}

运行会发现,即使主线程运行结束了,子线程依旧在持续运行;持续运行的子线程就称为前台线程
一般来说,只有等待前台线程运行完毕后,程序才可以进行关闭。
与之对应的是 后台线程,可以通过thread.IsBackground = true;//切换为后台线程将前台 线程切换到后台线程,这样再次运行会发现,当主线程结束后,后台线程就会被强制结束。

一般来说,前台线程用于需要时间比较长的等待业务,比如监听客户端请求,而后台线程适用于时较短的业务比如执行客户端发来的请求,后台进程不会影响程序的终止。
托管在线程池中的线程全部为后台线程
所有使用new Thread创建的线程默认均为前台线程

三、线程池

示例代码

for(int i = 0;i < 100; i++)
{ThreadPool.QueueUserWorkItem((o) =>{Console.WriteLine($"循环次数{i} 线程id {Thread.CurrentThread.ManagedThreadId}");});
}

线程池执行结果
可以看到执行结果出现了id重复的状况,原因就是线程池会重复使用已经完成的线程,极大节约硬件资源。
另外,可以看到,for循环有100次,但是从输出结果来看,只执行了十几次,原因为线程池创建的线程均为后台线程,只要主程序退出,线程池的后台线程就会被停止,而主程序main执行的时间很短,因此线程池内线程没有来得及执行就被停止了。

对于重要的并发量小的线程,需要手动创建管理,对于并发量大而又不太重要的线程,最好托管到线程池中。

四、结束线程与CancellationToken

不管程序有多少个进程,进程内部的资源都是被共享的。所以C#对进程的取消代码作了更高层次的抽象,把进程的取消过程封装成为了Token的形式,也就是CancellationToken(取消令牌)。不仅可以使用在多线程中,还可以用于异步操作。

class Program
{static void Main(string[] args){CancellationTokenSource cts = new CancellationTokenSource();Thread thread = new Thread(() => { PrintHello(cts.Token); });thread.Start();//下载文件Thread.Sleep(5000);//关闭子进程//cts.Cancel();cts.CancelAfter(3000);//在下载完成后3s失效Console.WriteLine("退出主程序");}private static void PrintHello(CancellationToken tokenSource){while (!tokenSource.IsCancellationRequested){Thread.Sleep(1000);Console.WriteLine("Hello from PrintHello!");}}
}

五、Join与IsAlive

对于子线程执行时间不确定的情况,需要使用Join的方法,加入至主程序执行中,或者使用IsAlive方法进行判断

class Program
{static void Main(string[] args){Thread thread = new Thread(() => { PrintHello(); });thread.Start();//方法一//thread.Join();//方法二while(thread.IsAlive){Console.WriteLine("子线程仍在工作");Thread.Sleep(100);}Console.WriteLine("退出主程序");}private static void PrintHello(){int i = 0;while (i++ < 10){Thread.Sleep(new Random().Next(100, 1000));Console.WriteLine("Hello from PrintHello!");}}
}

六、资源竞争与线程锁lock

使用线程可以并发的在CPU的核心中执行任务,最大化CPU的利用率,但是并发执行任务也可能产生各种各样的资源竞争问题。

举例:

    private static void AddText(){File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}");Thread.Sleep(100);File.AppendAllText((@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}");}

当两个线程同时需要使用同一个文件资源时,产生资源竞争,导致系统崩溃。
因此必须保证同一时刻只能有一个线程访问资源,避免出现资源恶性竞争。
使用线程锁就可以解决

class Program
{static object lockedObj = new object();static void Main(string[] args){for(int i = 0; i < 10; i++){var t = new Thread(AddText); t.Start();}Console.WriteLine("退出主程序");}private static void AddText(){lock(lockedObj){File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}");Thread.Sleep(100);File.AppendAllText(@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}");}           }
}

七、异步

在之前项目中,我们实现的所有操作都是同步进行的,然而当有同时10000个请求发生时,会使得用户有很长的等待,服务器会等待数据库的响应,完成后反馈至用户。
而异步操作要实现,不要等待数据库,继续执行下一个请求,当数据返回数据以后,再回头继续处理上一个请求。
然而对于更高级别的数量请求,仅仅依靠异步也是不够的,因此需要:
异步服务+每个机器多开进程+多个机器组合实现;
K8s, Kubernetess容器化分布式部署;
.NET Core对容器化非常非常友好、支持度极高

八、异步编程Task

我们使用异步处理并行,使用多线程处理并发。

异步逻辑是要基于方法没有依赖关系的,例如

 class Program{static void Main(string[] args){Calculate();Console.Read();}static void Calculate(){//Task->异步,Thread->线程Task.Run(() =>{Calculate1();});Task.Run(() =>{Calculate2();});Task.Run(() =>{Calculate3();});}static int Calculate1(){var result = 3;Console.WriteLine($"Calculate1: {result}");Task.Delay(2000);return result;}static int Calculate2(){var result = 4;Console.WriteLine($"Calculate2: {result}");Task.Delay(3000);return result;}static int Calculate3(){var result = 5;Console.WriteLine($"Calculate3: {result}");Task.Delay(1000);return result;}}

但如果是具有依赖关系的,例如,Caculate2()需要Caculate1()的结果,Caculate3需要Caculate1()和Caculate2()的结果,那么就需要做如下调整

class Program
{static void Main(string[] args){Calculate();Console.Read();}static void Calculate(){//Task->异步,Thread->线程var task1 = Task.Run(() =>{return Calculate1();});var awaiter1 = task1.GetAwaiter();//获得异步等待对象awaiter1.OnCompleted(() =>{var result1 = awaiter1.GetResult();//获得异步逻辑的最终计算结果var task2 = Task.Run(() =>{return Calculate2(result1);});var awaiter2 = task2.GetAwaiter();awaiter2.OnCompleted(() =>{var result2 = awaiter2.GetResult();var result = Calculate3(result1, result2);Console.WriteLine(result);});});          }static int Calculate1(){var result = 3;Console.WriteLine($"Calculate1: {result}");Task.Delay(2000);return result;}static int Calculate2(int a){var result = a * 2;Console.WriteLine($"Calculate2: {result}");Task.Delay(3000);return result;}static int Calculate3(int a, int b){var result = a + b;Console.WriteLine($"Calculate3: {result}");Task.Delay(1000);return result;}
}

九、C#的异步 async/await

可以看到,上面的异步操作代码非常复杂繁琐,接下来使用async/await化解上面操作

同步方法
指程序调用某个方法,需要等待执行完成以后才进行下一步操作
异步方法
指程序调用某个方法的时候,不做任何等待,在处理完成之前就返回该方法,继续执行接下来的操作,即函数在执行完成前就可以先返回调用方,然后继续执行接下来的逻辑完成任务的函数

举例:

public async Task<int> DoSomethingAsync()
{//创建一个计算1万毫秒的任务Task<int> longRunningTask = LongRunningTaskAsync();//使用await执行这个任务int result = await longRunningTask;return result;
}
//假装计算1w毫秒,输出为1
private async Task<int> LongRunningTaskAsync()
{await Task.Delay(10000);//延迟10sreturn 1;
}

1.需要使用async关键词
2.返回类型为:voidTaskTask<T>IAsyncEnumerable<T>
3.命名规范:Async结尾
4.需要有await表达式
5.要有返回值
6.async函数只能被async函数调用

共有三个部分:
第一部分异步调用:Task<int> longRunningTask = LongRunningTaskAsync();
第二部分执行异步:int result = await longRunningTask;
第三部分异步方法:private async Task<int> LongRunningTaskAsync(){}

注:

  • [ 在函数声明中,async关键字要放到返回类型之前 ]
  • [ async函数本身不创建异步操作,只有在调用await的时候才会进行异步操作 ]

下面对之前的异步代码进行优化:

class Program
{static void Main(string[] args){Calculate();Console.Read();}static async void Calculate(){var result1 = await Calculate1Async();var result2 = await Calculate2Async(result1);var result = await Calculate3Async(result1, result2);Console.WriteLine(result);          }static async Task<int> Calculate1Async(){var result = 3;Console.WriteLine($"Calculate1: {result}");await Task.Delay(2000);return result;}static async Task<int> Calculate2Async(int a){var result = a * 2;Console.WriteLine($"Calculate2: {result}");await Task.Delay(3000);return result;}static async Task<int> Calculate3Async(int a, int b){var result = a + b;Console.WriteLine($"Calculate3: {result}");await Task.Delay(1000);return result;}
}

十、Task VS. Thread

异步不是多线程!!!
异步用来处理并行,多线程用于处理并发

class Program
{static void Main(string[] args){TaskTest();ThreadTest();Console.Read();}static void TaskTest(){var sw = new Stopwatch();sw.Start();for(int i = 0; i < 100; i++){Task.Factory.StartNew(() => { });}sw.Stop();Console.WriteLine($"Task {sw.ElapsedMilliseconds}");}static void ThreadTest(){var sw = new Stopwatch();sw.Start();for (int i = 0; i < 100; i++){new Thread(() => { }).Start();}sw.Stop();Console.WriteLine($"Thread {sw.ElapsedMilliseconds}");}
}

执行结果为
Task VS. Thread执行结果
可以看到Task的执行速度要远高于Thread!
异步并不会创建线程,只是通过主线程来执行,同时开出一条分路来执行其他任务,非同步分别执行。但是在最后会创建一个非常轻量级的Worker Thread,用于通知主程序异步结束,也称为回调Call Back.


http://www.ppmy.cn/news/1354695.html

相关文章

[嵌入式系统-28]:开源的虚拟机监视器和仿真器:QEMU(Quick EMUlator)与VirtualBox、VMware Workstation的比较

目录 一、QEMU概述 1.1 QEMU架构 1.2 QEMU概述 1.3 什么时候需要QEMU 1.4 QEMU两种操作模式 1.5 QEMU模拟多种CPU架构 二、QEMU与其他虚拟机的比较 2.1 常见的虚拟化技术 2.1 Linux KVM 2.2 Windows VirtualBox 2.3 Windows VMware workstation 三、VirtualBox、VM…

【北邮鲁鹏老师计算机视觉课程笔记】03 edge 边缘检测

【北邮鲁鹏老师计算机视觉课程笔记】03 1 边缘检测 有几种边缘&#xff1f; ①实体上的边缘 ②深度上的边缘 ③符号的边缘 ④阴影产生的边缘 不同任务关注的边缘不一样 2 边缘的性质 边缘在信号突变的地方 在数学上如何寻找信号突变的地方&#xff1f;导数 用近似的方法 可以…

C#系列-Entity Framework 架构(18)

下图展示了EF的整体架构。现在让我们逐个地看看架构的各个组件&#xff1a; EF组件图 EDM&#xff08;Entity Data Mode 实体数据模型&#xff09;:EDM 由三个主要部分组成&#xff1a;概念模型&#xff0c;映射和存储模型。 Conceptual Model&#xff08;概念模型&#xff0…

使用消息中间件实现系统间的异步通信和解耦

​​​​​​​目录 引言 一. 选择合适的消息中间件 二. 定义消息格式和通信协议 1. 定义消息格式 消息头 消息体 2. 定义通信协议 发送消息 接收消息 消息处理 3. 示例代码 定义消息格式 发送消息 接收消息 三、发布-订阅模式 1. 定义发布-订阅模式 2. 示例代…

Python一级考试笔记

Python一级考试笔记【源源老师】 前置知识&#xff1a;&#xff08;了解即可&#xff09; Python常见的几种编程环境&#xff1a;IDLE&#xff08;自带&#xff09;、Visual Studio Code、Jupyter、pyCharm&#xff1b; python版本&#xff1a;python3 和 python2&#xff08;…

if中有return;,那if之后的语句还执行吗

好久没看代码了&#xff0c;这里的语句有点迷糊看不懂。 某方法中使用if作判断&#xff0c;执行语句块最后一句是“return;”&#xff0c;如果执行该语句&#xff0c;if后面的语句还会执行吗&#xff1f; 比如下面这个例子&#xff0c;if为true的情况下&#xff0c;控制台会打…

数据结构之时空复杂度

一、前言 1&#xff09;什么是数据结构 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的数据元素的 集合。 2&#xff09;什么是算法 算法(Algorithm):就是定义良好的计算过程&#xff0c;他取一个或一组的值为输入&am…

解锁Spring Boot中的设计模式—03.委派模式:探索【委派模式】的奥秘与应用实践!

委派模式 文章目录 委派模式1.简述**应用场景****优缺点****业务场景示例** 2.类图3.具体实现3.1.自定义注解3.2.定义抽象委派接口3.3.定义具体执行者3.4.定义委派者(统一管理委派任务)3.5.定义委派者管理类 4.测试4.1.controller层4.2.测试不同场景4.2.1.测试生产部门计算费用…