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

一、什么是线程

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

在一个进程中同时启用多个线程并行操作,就叫做多线程
由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.rhkb.cn/news/259064.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ STL: list使用及源码剖析

list使用 list常用函数及使用&#xff08;1&#xff09; #include <iostream> #include <list> #include <algorithm>int main() {// 创建liststd::list<int> myList {5, 2, 9, 1, 5, 6};// 打印liststd::cout << "Original list: &quo…

【论文精读】GPT2

摘要 在单一领域数据集上训练单一任务的模型是当前系统普遍缺乏泛化能力的主要原因&#xff0c;要想使用当前的架构构建出稳健的系统&#xff0c;可能需要多任务学习。但多任务需要多数据集&#xff0c;而继续扩大数据集和目标设计的规模是个难以处理的问题&#xff0c;所以只能…

鸿蒙OS跨进程IPC与RPC通信

一、IPC与RPC通信概述 基本概念 IPC&#xff08;Inter-Process Communication&#xff09;与RPC&#xff08;Remote Procedure Call&#xff09;用于实现跨进程通信&#xff0c;不同的是前者使用Binder驱动&#xff0c;用于设备内的跨进程通信&#xff0c;后者使用软总线驱动…

【PyQt】在PyQt5的界面上集成matplotlib绘制的图像

文章目录 0 前期教程1 概述2 matplotlib2.1 库导入2.2 图片的各个部分解释2.3 代码风格2.4 后端 3 集成matplotlib图像到pyqt界面中3.1 使用到的模块3.2 理解Qt Designer中的“控件提升”3.3 界面与逻辑分离的思路3.4 扩展 0 前期教程 【PyQt】PyQt5进阶——串口上位机及实时数…

vscode

vscode个人使用过程-仅供个人参考。 vscode代码提示-修改首行为abc的提示解决方法 问题描述&#xff1a; 比如console.log这个常用的打印代码 可是当使用后会发现一个问题&#xff0c;有一个abc的代码提示永远在第一行 解决方法&#xff1a; vscode设置-->搜索栏输入ed…

【设计模式】23中设计模式笔记

设计模式分类 模板方法模式 核心就是设计一个部分抽象类。 这个类具有少量具体的方法&#xff0c;和大量抽象的方法&#xff0c;具体的方法是为外界提供服务的点&#xff0c;具体方法中定义了抽象方法的执行序列 装饰器模式 现在有一个对象A&#xff0c;希望A的a方法被修饰 …

基于GPT一键完成数据分析全流程的AI Agent: Streamline Analyst

大型语言模型&#xff08;LLM&#xff09;的兴起不仅为获取知识和解决问题开辟了新的可能性&#xff0c;而且催生了一些新型智能系统&#xff0c;例如旨在辅助用户完成特定任务的AI Copilot以及旨在自动化和自主执行复杂任务的AI Agent&#xff0c;使得编程、创作等任务变得高效…

GPT-4对编程开发的支持

在编程开发领域&#xff0c;GPT-4凭借其强大的自然语言理解和代码生成能力&#xff0c;能够深刻理解开发者的意图&#xff0c;并基于这些需求提供精准的编程指导和解决方案。对于开发者来说&#xff0c;GPT-4能够在代码片段生成、算法思路设计、模块构建和原型实现等方面给予开…

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱10(附带项目源码)

效果演示 文章目录 效果演示系列目录前言战利品箱子源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中&#xff0c;我们将探索如何用unity制作一个3D背包、库存、制作、快捷栏、…

OpenAI全新发布的Sora,到底意味着什么?

16日凌晨&#xff0c;OpenAI发布了文本视频的工具&#xff08;text-do-video&#xff09;Sora&#xff0c;整个世界再次被震撼。 Sora的出现&#xff0c;到底意味着什么&#xff1f; 目录 Sora的背景与概述Sora是什么&#xff1f;能为我们做些什么&#xff1f;存在的一些问题 文…

一、springBoot入门

一、springBoot入门 步骤一&#xff1a;分析 建立一个需求&#xff1a;使用 SpringBoot 开发一个web应用&#xff0c;浏览器发起请求 /hello后&#xff0c;给浏览器返回字符串“hello worid ~"。 构建步骤概况 创建Maven攻城导入spring-boot-stater-web起步依赖编写Cont…

【开源】SpringBoot框架开发学校热点新闻推送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 新闻类型模块2.2 新闻档案模块2.3 新闻留言模块2.4 新闻评论模块2.5 新闻收藏模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 新闻类型表3.2.2 新闻表3.2.3 新闻留言表3.2.4 新闻评论表3.2.5 新闻收藏表 四、系统展…

Arduino的PWM功能应用:会呼吸的灯

目录 概述 1 认识PWM 1.1 PWM原理 1.2 PWM的应用 1.3 在Arduino中使用PWM 2.硬件 2.1 硬件结构 2.2 线路连接 3 软件 3.1 编译和下载代码 3.2 详细代码 4 测试 4.1 灯的变化测试 4.2 使用逻辑分析仪看波形 概述 本文通过一个简单的案例&#xff0c;介绍Arduino中P…

Java 学习和实践笔记(11)

三大神器&#xff1a; 官方网址: http://www.jetbrains.com/idea/ 官方网址: https://code.visualstudio.com/ 官方网址: http://www.eclipse.org 装好了idea社区版&#xff0c;并试运行以下代码&#xff0c;OK&#xff01; //TIP To <b>Run</b> code, press &l…

WebServer 之 http连接处理(下)

目录 ✊请求报文--解析 流程图 && 状态机 状态机 -- 状态转移图 主状态机 从状态机 http 报文解析 HTTP_CODE 含义 从状态机 逻辑 主状态机 逻辑 &#x1f41e;请求报文--响应 基础API stat mmap iovec writev 流程图 HTTP_CODE 含义(2) 代码分析 …

HTTP缓存技术

大家好我是苏麟 , 今天说说HTTP缓存技术 . 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP缓存技术 HTTP 缓存有哪些实现方式? 对于一些具有重复性的 HTTP 请求&#xff0c;比如每次请求得到的数据都一样的&#xff0c;我们可以把这对「请求-响…

Anaconda修改虚拟环境的路径

新版本的anaconda会默认将虚拟环境配置在C盘下&#xff0c;默认的路径是C:\Users\username。同时anaconda3下envs目录是空的。 这里是建立虚拟环境是将路径修改到anaconda的方法。 第一步——修改.condarc文件 首先&#xff0c;C:\Users\username找到.condarc文件 添加或修…

004 - Hugo, 分类

004 - Hugo, 分类content文件夹 004 - Hugo, 分类 content文件夹 ├─.obsidian ├─categories │ ├─Python │ └─Test ├─page │ ├─about │ ├─archives │ ├─links │ └─search └─post├─chinese-test├─emoji-support├─Git教程├─Hugo分类├─…

数学建模【多目标规划】

一、多目标规划简介 多目标规划的本质是“既要XXX又要XXX”&#xff0c;而不论是线性规划还是非线性规划都是一个目标函数&#xff0c;例如工业生产产品&#xff0c;追求最大化利润等。但是多目标规划存在多个目标&#xff0c;可以转化出多个目标函数&#xff0c;故难点在同时…

电商行业的机遇在哪?致淘宝平台API数据接口

在电商行业蓬勃发展的今天&#xff0c;我们不得不提及淘宝这个伟大的平台。它不仅为亿万用户提供了便捷的购物体验&#xff0c;更为无数的商家创造了一个财富的聚集地。而如今&#xff0c;随着技术的不断进步&#xff0c;淘宝开放了其强大的API接口&#xff0c;为广大开发者带来…