.Net 多线程、异步、性能优化应用与心得

文章目录

    • 概要
    • 多线程
      • Thread方式创建线程:
      • Task方式创建线程[C#5.0引入](推荐使用):
      • 线程池方式创建线程:
    • 异步
      • 异步方法
      • 异步IO操作
      • 异步数据库操作
      • 异步Web请求
      • 取消异步
      • ValueTask[C# 7.0引入]
        • ValueTask<TResult> 和 Task
    • 性能优化
      • 懒加载对象
      • 循环
        • List<T>.ForEach
        • Foreach(推荐)
        • for
    • 小结

概要

本文主要介绍在.net 8中如何进行大数据处理会用到多线程,异步,以及性能优化,读写锁,缓存等相关知识。

多线程

在处理大量数据的时候往往对处理速度是有需求的,越快越好。如果想要快的话的我们必定会想到多线程,然而在.net Core中线程的创建模式多种多样,我们改选择哪一种呢?每一种创建方式的各有什么优缺点呢?

Thread方式创建线程:

 static void Main(){List<Thread> threads = new List<Thread>();List<string> val = new List<string>() { "参数1", "参数2", "参数3", "参数4", "参数5", "参数6" };foreach (var item in val){var thread= new Thread(() => { Console.WriteLine("这是线程:"+ item); });threads.Add(thread);thread.Start();}foreach (var thread in threads){thread.Join();}}

执行效果:
在这里插入图片描述
这种方式能保证线程执行顺序,因为我们后面是用的join(),这种方式会创建六个线程用完之后就会被销毁掉。
如果数据量少和下面说的Task创建方式体现不出来太明显的差距,但是如果数据量大的话,频繁的创建和销毁线程肯定是行不通的。

Task方式创建线程[C#5.0引入](推荐使用):

static async Task Main(string[] args){List<Task> tasks = new List<Task>();List<string> val = new List<string>() { "参数1", "参数2", "参数3", "参数4", "参数5", "参数6" };foreach (var item in val){var task= Task.Factory.StartNew(() => { Console.WriteLine("这是:"+ item); });tasks.Add(task);}await Task.WhenAll(tasks);}

执行效果:
在这里插入图片描述
这种方式可以看出来这六个线程明显没有顺序可言。相对于上面说的Thread方式这种方式的执行其实是并行的。
而且这种方式的底层实现其实是才用的线程池,当线程使用完之后不会立即销毁,会放回线程池内,等到下次再处理的时候就可以直接使用这个线程,这样就避免了频繁的创建和销毁线程,减少了性能开销。当然它也不是一直存放在线程池里就不销毁了,如果真是这样那迟早得爆炸,线程池会自动维护它,当它闲置了较长时间之后也是会被自动销毁掉的。

线程池方式创建线程:

 static void Main(){int ThreadNumber = 6;var doneEvents = new ManualResetEvent[ThreadNumber];List<string> val = new List<string>() { "参数1", "参数2", "参数3", "参数4", "参数5", "参数6" };foreach (var item in val){int _index = val.IndexOf(item);doneEvents[_index] = new ManualResetEvent(false);ThreadPool.QueueUserWorkItem((state) =>{int _index = val.IndexOf(item);Console.WriteLine(val[_index]);doneEvents[_index].Set();}, item);}WaitHandle.WaitAll(doneEvents);}

执行效果:
在这里插入图片描述
这种方式也是没有顺序的,其实和task效果是一样的,因为task底层就是才用的线程池实现的,但是这种方式相对于Task来说更为麻烦。

异步

聊完了多线程咱们再来聊一聊在.net Core中很常见的异步,异步的使用想必大家都会吧,在此之前我想提醒大家一下
如果方法的处理速度很快,或者你的代码执行后立即可用等,使用异步并不会比同步快,反而有可能多消耗性能资源

异步方法

public async Task<int> GetNumberAsync()
{await Task.Delay(1000); //模拟长时间运行的任务return 42;
}

这里 async 和await 底层逻辑是通过状态机实现的分段执行。当一个方法被标记为async时,编译器会生成一个状态机类,该类实现了IAsyncStateMachine接口。状态机负责管理异步操作的执行流程,包括启动、暂停、继续和完成等状态‌,方法上加了async编译器就会生成两个方法,一个同步方法一个异步方法,程序执行的时候会调用同步方法。
同步方法中再调用生成的那个异步方法,异步方法则会创建了一个状态机,将参数传给状态机,并调用Start方法,可知异步方法实际上是状态机方法的调用。
不太理解的话可以看一看这位博主的文章写得很详细的 传送门

异步IO操作

Stopwatch stopwatch = Stopwatch.StartNew();//记录时间string FilePath = "E:\\测试项目\\Test";string FileName = "测试文本写入.txt";for (int i = 0; i < 10; i++){await Task.Run(async () =>{await Console.Out.WriteLineAsync("当前线程ID:"+ Thread.CurrentThread.ManagedThreadId);var Write = await ReadWriteFileAsync(FilePath, FileName, $"测试写入{i}");await Console.Out.WriteLineAsync(Write.Item2);var Read = await ReadFileAsync(FilePath + "\\" + FileName);await Console.Out.WriteLineAsync(Read.Item2);Console.ForegroundColor = ConsoleColor.Green;await Console.Out.WriteLineAsync(Read.Item2);Console.ResetColor();});}stopwatch.Stop();Console.WriteLine($"任务执行耗时: {stopwatch.ElapsedMilliseconds} 毫秒");static async Task<(bool,string)> ReadFileAsync(string filePath){try{string content = await File.ReadAllTextAsync(filePath);return(true,"读取成功:"+content);}catch (Exception e){return  (false,$"读取文件失败:{e.Message}");}}static async Task<(bool,string)> ReadWriteFileAsync(string filePath,string fileName,string text){if(string.IsNullOrWhiteSpace(filePath)&&string.IsNullOrWhiteSpace(fileName)){return (false,"路径或文件名称不能为空!");}if(!fileName.Contains(".")){return (false, "文件名称需要加上文件类型后缀!");}try{string path =$"{filePath}\\{fileName}";if (File.Exists(path)){//文件已存在await File.AppendAllTextAsync(path, text);return (true, "追加成功!");}else{//文件未存在await File.WriteAllTextAsync(path, text);return (true, "写入成功!");}}catch (Exception ex){return (false, "写入错误:" + ex.Message);}} Console.ReadLine();

执行效果: 在这里插入图片描述
这里我是记录的执行耗时的,总共花费了97毫秒,其实多数耗时都是花费在了创建线程上面,采用多线程的方式去进行文件的写入和读取,可以看出异步操作不会阻塞调用线程,适合在高并发场景下提高程序的整体性能,如果每次写入都是一个用户发起的请求的话那么也可以说它可以更有效地利用系统资源,比如在网络应用中,可以处理多个网络连接而不会阻塞。

异步数据库操作

常见的ORM几乎都提供的异步操作的方法。像SqlSugar、EF Core、Dapper 都提供的异步查询相关的异步方法。
各自的官方也有相关的介绍,这里就不过多赘述了,具体实现细节都各有千秋,有兴趣的同学可以研究他们的源代码。
在异步操作时需要注意并发问题,比如在高并发时同时去修改同一条数据,一定要用主键作为条件,这样可以减少事务死锁发生的概率。用主键的话数据库表使用的是行锁,如果时用到主键外的其他条件进行判断则会锁表

异步Web请求

.net Core 中有三种发起Http请求的方式

  • HttpWebRequest
    它在System.Net命名空间下。它派生自WebRequest, 这个类非常强大,强大的地方在于它封装了几乎HTTP请求报文里需要用到的东西,以致于能够能够发送任意的HTTP请求并获得服务器响应(Response)信息。采集信息常用到这个类。但是对于新手小白来说它的配置又太复杂了。而且想要使用现代化异步编程的话实现起来也不太方便。

  • HttpClient
    这个方法要慎用,用不好就会TCP 连接和疯狗一样向上猛蹿。可以看看这位博主写的文章:传送门

  • IHttpClientFactory(推荐

IHttpClientFactory 是在 .NET Core 2.1 版本引入的,用于创建和管理 HttpClient 实例的新型机制。它提供了中心化的配置,管理 Logging 和 Distributed caching 的能力,以及客户端的注册和复用 可以方便地管理HTTP客户端的生命周期,例如,通过依赖注入容器来管理,可以自动处理依赖关系和连接池。支持外部配置,如负载均衡,长时间运行的HTTP连接,以及服务发现。

三种方式的使用方式:传送门

取消异步

在这里插入图片描述

很多异步方法参数列表中都会有 CancellationToken 这个参数,那这个参数的作用是什么呢?想必大家已经猜到了它就是用来取消异步的关键。
那么帅气的彦祖们又会提问了,好好的异步取消它干啥?
比如说异步下载文件的时候异步请求超时了、异步查询数据库时连接超时等情况就需要结束异步了,避免一直耗费性能。
它是一个轻量级对象,可以通知请求是否已取消,我们可以手动调用 它里面的Cancel()方法来取消任务

示例代码(摘抄自: 天才卧龙):

static async Task Main(string[] args){CancellationTokenSource source = new CancellationTokenSource();source.CancelAfter(4 * 1000);//运行时间超过4秒,则取消执行try{await DownLoadPage_3("http://www.baidu.com", 200, source.Token);//输入q 请求被取消while (Console.ReadLine() == "q"){source.Cancel();}}catch{ Console.WriteLine($"下载超时被取消了");}}//简单的下载任务public static async Task DownLoadPage_3(string uri, int num, CancellationToken token){using (HttpClient client = new HttpClient()){for (int i = 0; i < num; i++){var html = await client.GetAsync(uri, token);Console.WriteLine($"第{i + 1}次下载");//抛出被取消的异常token.ThrowIfCancellationRequested();}}}

ValueTask[C# 7.0引入]

ValueTask 和 Task

ValueTask 存在于 System.Threading.Tasks 命名空间下,ValueTask 的定义如下:
在这里插入图片描述

IEquatable<T> 接口定义 Equals 方法,用于确定两个实例是否相等。
Task 的定义如下:

public class Task : IAsyncResult, IDisposable

微软官方文档的大概意思就是ValueTask这个类型,应该是 Task 的简化版本,Task 是引用类型,因此从异步方法返回 Task 对象或者每次调用异步方法时,都会在托管堆中分配该对象。
这里我们就可以总结出
这就是它俩主要的不同之处

	Task 是引用类型,会在托管堆中分配内存;ValueTask 是值类型;

ValueTask 使用方法:

 static async ValueTask<int> StartAsync(int val)
{Task<int> task = Task.Run<int>(() => val + 1);return await new ValueTask<int>(task);
}
int val= await  StartAsync(3);
Console.WriteLine("返回值"+val);
Console.ReadLine();

执行效果:
在这里插入图片描述
如果想更深入了解的话可以去看看这位大佬写的文章:溪源More

性能优化

懒加载对象

想要懒加载对象可以用Lazy类来实现。Lazy可以确保在实际访问对象之前不会创建它
如下代码中new Lazy的时候是不会创建对象的直到访问.Value属性才会创建相关对象。

示例代码:

Lazy<CW> lazyObject = new Lazy<CW>();// 在实际访问对象之前,不会创建它
if (lazyObject.IsValueCreated)
{Console.WriteLine("对象已经被创建");
}
else
{Console.WriteLine("对象尚未被创建");
}
// 下面的代码将触发对象的创建
CW actualObject = lazyObject.Value;
Console.WriteLine("程序结束");
Console.ReadLine();public class CW
{public CW(){// 这里可以是耗时的初始化代码Console.WriteLine("对象 被创建");}
}

执行效果
在这里插入图片描述

循环

List.ForEach

List.ForEach是List类中的一个方法,允许你对列表中的每个元素执行一个指定的懂做,通过传递一个Action委托,它极大的简化了代码的编写,比如假设我们有一个List想打野出每个元素可以这样写

List<string> strs = new List<string>() { "字符串1", "字符串2", "字符串3", "字符串4" };
strs.ForEach(t => Console.WriteLine(t));

执行效果
在这里插入图片描述

Foreach(推荐)

C# 中的关键字,能够遍历任何实现了IEnumerable接口的集合

List<string> strs = new List<string>() { "字符串1", "字符串2", "字符串3", "字符串4" };
foreach (string str in strs)
{Console.WriteLine(str);
}

执行效果:
在这里插入图片描述

for

for循环是一种控制结构,用于重复执行一组语句,直到指定的条件返回false‌

List<string> strs = new List<string>() { "字符串1", "字符串2", "字符串3", "字符串4" };
for (int i = 0; i < strs.Count; i++)
{Console.WriteLine(strs[i]);
}

三种方式的性能对比

for循环和foreach对决:

  • 功能上的区别:foreach用于遍历集合,而for除了遍历集合,还可以用于执行一系列固定次数的迭代操作。
  • 性能上的区别:在遍历集合时,如果你不需要知道元素的索引,通常建议使用foreach,因为这样更简洁,更易于阅读。在需要访问索引时,for循环更为合适。但在性能上,两者几乎没有差异。
  • 用法上的区别:foreach语法更简洁,不需要在循环体中显式地处理索引的增减。而for循环需要显式地控制循环变量的初始化、循环条件和迭代操作。

foreach和List.ForEach对决:

  • 性能上的区别:List.ForEach在遍历的时候会创建额外的委托实力,因此在大量数据处理的时候使用foreach效率会更高一些
  • 灵活性的区别:foreach 可以在循环中使用 break 或 continue 来控制循环的进行。相对而言,List.ForEach 则缺乏这种直接控制的能力,只能在回调函数中执行具体逻辑。
  • 可读性的区别:List.ForEach 更像是函数式编程的风格,特别适合处理集合中的操作。而 foreach 提供了更直观、可读的语法。

小结

长路漫漫,学习编程就是一个不断学习成长的过程,就像游戏一样打怪升级。一定要保持住那份探索的热情。加油我的朋友!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/488844.html

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

相关文章

qt-C++语法笔记之mapToGlobal将组件(控件)中的本地坐标系(局部坐标)映射到全局坐标系

qt-C语法笔记之mapToGlobal将组件&#xff08;控件&#xff09;中的本地坐标系&#xff08;局部坐标&#xff09;映射到全局坐标系 code review! 文章目录 qt-C语法笔记之mapToGlobal将组件&#xff08;控件&#xff09;中的本地坐标系&#xff08;局部坐标&#xff09;映射到…

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…

UniScene:Video、LiDAR 和Occupancy全面SOTA

论文: https://arxiv.org/pdf/2412.05435 项目页面&#xff1a;https://arlo0o.github.io/uniscene/ 0. 摘要 生成高保真度、可控制且带有标注的训练数据对于自动驾驶至关重要。现有方法通常直接从粗糙的场景布局生成单一形式的数据&#xff0c;这不仅无法输出多样化下游任务…

Ubuntu22.04搭建FTP服务器保姆级教程

在网络环境中&#xff0c;文件传输是一项至关重要的任务。FTP&#xff08;文件传输协议&#xff09;是一种基于客户端/服务器模式的协议&#xff0c;广泛用于在互联网上传输文件。Ubuntu作为一款流行的Linux发行版&#xff0c;因其稳定性和易用性而广受开发者和系统管理员的喜爱…

【银河麒麟高级服务器操作系统】修改容器中journal服务日志存储位置无效—分析及解决方案

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 服务器环境以及配置 【机型】 整机类型/架构&am…

React 第十六节 useCallback 使用详解注意事项

useCallback 概述 1、useCallback 是在React 中多次渲染缓存函数的 Hook&#xff0c;返回一个函数的 memoized的值&#xff1b; 2、如果多次传入的依赖项不变&#xff0c;那么多次定义的时候&#xff0c;返回的值是相同的,防止频繁触发更新&#xff1b; 3、多应用在 父组件为函…

二十七、Tomcat专题总结与拓展

文章目录 一、Tomcat设计思路总结1、Tomcat整体架构2、Tomcat设计思路 二、Tomcat源码设计精髓三、拓展&#xff1a;SpringBoot整合Tomcat源码分析四、拓展&#xff1a;SpringBoot整合Undertow实战1、Undertow概述2、SpringBoot集成Undertow2.1、引入依赖2.2、application.prop…

[游戏开发] Unity中使用FlatBuffer

什么是FlatBuffer 官网&#xff1a; GitHub - google/flatbuffers: FlatBuffers: Memory Efficient Serialization LibraryFlatBuffers: Memory Efficient Serialization Library - google/flatbuffershttps://github.com/google/flatbuffers 为什么用FloatBuffer&#xff0c…

【JAVA】旅游行业中大数据的使用

一、应用场景 数据采集与整合&#xff1a;全面收集旅游数据&#xff0c;如客流量、游客满意度等&#xff0c;整合形成统一数据集&#xff0c;为后续分析提供便利。 舆情监测与分析&#xff1a;实时监测旅游目的地的舆情信息&#xff0c;运用NLP算法进行智能处理&#xff0c;及…

android studio创建虚拟机注意事项

emulator 启动模拟器的时候&#xff0c;可以用 AVD 界面&#xff0c;也可以用命令行启动&#xff0c;但命令行启 动的时候要注意&#xff0c;系统有两个 emulator.exe &#xff0c;建议使用 emulator 目录下的那个&#xff01;&#xff01; 创建类型为google APIs的虚拟机可从…

全面解析租赁小程序的功能与优势

内容概要 租赁小程序正在逐渐改变人与物之间的互动方式。通过这些小程序&#xff0c;用户不仅可以轻松找到所需的租赁商品&#xff0c;还能够享受无缝的操作体验。为了给大家一个清晰的了解&#xff0c;下面我们将重点介绍几个核心功能。 建议&#xff1a;在选择租赁小程序时&…

JCR一区牛顿-拉夫逊优化算法+分解对比!VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测

JCR一区牛顿-拉夫逊优化算法分解对比&#xff01;VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测 目录 JCR一区牛顿-拉夫逊优化算法分解对比&#xff01;VMD-NRBO-Transformer-BiLSTM多变量时序光伏功率预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.中科院…

用二维图像渲染3D场景视频

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

每日一练:链表-重排链表

LCR 026. 重排链表 - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln-1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ……

国产物联网平台(IotSharp+IoTGateway+Influxdb)快速上手

环境说明&#xff1a; Visual Studio 2022 CommunityIotSharp代码&#xff1a;https://github.com/IoTSharp/IoTSharp.gitIoTGateway版本&#xff1a;v2.1.1Node版本&#xff1a;v20.18.1Influxdb版本&#xff1a;v2.7.11 安装Node Node.js官网 官网下载并安装&#xff0c;…

每日一刷——二叉树的构建——12.12

第一题&#xff1a;最大二叉树 题目描述&#xff1a;654. 最大二叉树 - 力扣&#xff08;LeetCode&#xff09; 我的想法&#xff1a; 我感觉这个题目最开始大家都能想到的暴力做法就是遍历找到数组中的最大值&#xff0c;然后再遍历一遍&#xff0c;把在它左边的依次找到最大…

Redis篇-6--原理篇5--单线程模型

1、概述 Redis 采用单线程模型来处理客户端请求&#xff0c;这意味着在任意时刻只有一个命令被执行。这种设计简化了 Redis 的实现&#xff0c;并确保了高并发环境下的数据一致性。尽管 Redis 是单线程的&#xff0c;但它通过高效的内存管理和网络 I/O 操作&#xff0c;仍然能…

【问题记录】07 MAC电脑,使用FileZilla(SFTP)连接堡垒机不成功

项目场景&#xff1a; 使用MAC电脑&#xff0c;以子账号&#xff08;非root&#xff09;的形式登录&#xff0c;连接堡垒机CLB&#xff08;传统型负载均衡&#xff09;&#xff0c;使用FileZilla&#xff08;SFTP&#xff09;进行FTP文件传输。 问题描述&#xff1a; MAC电脑…

Linux下进程替换exec系列接口

文章目录 Linux下进程替换1. c库exec函数族一、exec函数族简介二、exec函数族函数原型及参数说明三、exec函数族的工作机制四、注意事项五、示例代码 2. 系统调用execve接口一、execve接口与C库exec函数族的关系二、函数原型三、参数说明四、工作原理五、返回值六、注意事项七、…

网页爬虫技术全解析:从基础到实战

引言 在当今信息爆炸的时代&#xff0c;互联网上的数据量每天都在以惊人的速度增长。网页爬虫&#xff08;Web Scraping&#xff09;&#xff0c;作为数据采集的重要手段之一&#xff0c;已经成为数据科学家、研究人员和开发者不可或缺的工具。本文将全面解析网页爬虫技术&…