NetCore并发编程
NetCore并发编程
示例代码:https://github.com/lotapp/BaseCode/tree/master/netcore/4_Concurrency
先简单说下概念(其实之前也有说,所以简说下):
- 并发:同时做多件事情
- 多线程:并发的一种形式
- 并行处理:多线程的一种(线程池产生的一种并发类型,eg:异步编程)
- 响应式编程:一种编程模式,对事件进行响应(有点类似于JQ的事件)
Net里面很少用进程,在以前基本上都是 线程+池+异步+并行+协程
我这边简单引入一下,毕竟主要是写Python的教程,Net只是帮你们回顾一下,如果你发现还没听过这些概念,或者你的项目中还充斥着各种 Thread
和 ThreadPool
的话,真的得系统的学习一下了,现在官网的文档已经很完善了,记得早几年啥都没有,也只能挖那些外国开源项目:
https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-processing-and-concurrency
1.异步编程(Task)
Task的目的其实就是为了简化 Thread
和 ThreadPool
的代码,下面一起看看吧:
异步用起来比较简单,一般IO,DB,Net用的比较多,很多时候都会采用重试机制,举个简单的例子:
/// <summary>/// 模拟一个网络操作(别忘了重试机制)/// </summary>/// <param name="url">url</param>/// <returns></returns>private async static Task<string> DownloadStringAsync(string url){ using (var client = new HttpClient()) { // 设置第一次重试时间 var nextDelay = TimeSpan.FromSeconds(1); for (int i = 0; i < 3; i++) { try { return await client.GetStringAsync(url); } catch { } await Task.Delay(nextDelay); // 用异步阻塞的方式防止服务器被太多重试给阻塞了 nextDelay *= 2; // 3次重试机会,第一次1s,第二次2s,第三次4s } // 最后一次尝试,错误就抛出 return await client.GetStringAsync(url); }}
然后补充说下Task异常的问题,当你await的时候如果有异常会抛出,在第一个await处捕获处理即可
如果 async
和 await
就是理解不了的可以这样想: async
就是为了让 await
生效(为了向后兼容)
对了,如果返回的是void,你设置成Task就行了,触发是类似于事件之类的方法才使用void,不然没有返回值都是使用Task
项目里经常有这么一个场景:等待一组任务完成后再执行某个操作,看个引入案例:
/// <summary>/// 1.批量任务/// </summary>/// <param name="list"></param>/// <returns></returns>private async static Task<string[]> DownloadStringAsync(IEnumerable<string> list){ using (var client = new HttpClient()) { var tasks = list.Select(url => client.GetStringAsync(url)).ToArray(); return await Task.WhenAll(tasks); }}
再举一个场景:同时调用多个同效果的API,有一个返回就好了,其他的忽略
/// <summary>/// 2.返回首先完成的Task/// </summary>/// <param name="list"></param>/// <returns></returns>private static async Task<string> GetIPAsync(IEnumerable<string> list){ using (var client = new HttpClient()) { var tasks = list.Select(url => client.GetStringAsync(url)).ToArray(); var task = await Task.WhenAny(tasks); // 返回第一个完成的Task return await task; }}
一个async方法被await调用后,当它恢复运行时就会回到原来的上下文中运行。
如果你的Task不再需要上下文了可以使用: task.ConfigureAwait(false)
,eg:写个日记还要啥上下文?
逆天的建议是:在核心代码里面一种使用 ConfigureAwait
,用户页面相关代码,不需要上下文的加上
其实如果有太多await在上下文里恢复那也是比较卡的,使用 ConfigureAwait
之后,被暂停后会在线程池里面继续运行
再看一个场景:比如一个耗时操作,我需要指定它的超时时间:
/// <summary>/// 3.超时取消/// </summary>/// <returns></returns>private static async Task<string> CancellMethod(){ //实例化取消任务 var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(3)); // 设置失效时间为3s try { return await DoSomethingAsync(cts.Token); } // 任务已经取消会引发TaskCanceledException catch (TaskCanceledException ex) { return "false"; }}/// <summary>/// 模仿一个耗时操作/// </summary>/// <returns></returns>private static async Task<string> DoSomethingAsync(CancellationToken token){ await Task.Delay(TimeSpan.FromSeconds(5), token); return "ok";}
异步这块简单回顾就不说了,留两个扩展,你们自行探讨:
- 进度方面的可以使用
IProgress<T>
,就当留个作业自己摸索下吧~ - 使用了异步之后尽量避免使用
task.Wait
ortask.Result
,这样可以避免死锁
Task其他新特征去官网看看吧,引入到此为止了。
2.并行编程(Parallel)
这个其实出来很久了,现在基本上都是用 PLinq
比较多点,主要就是:
- 数据并行:重点在处理数据(eg:聚合)
- 任务并行:重点在执行任务(每个任务块尽可能独立,越独立效率越高)
数据并行
以前都是 Parallel.ForEach
这么用,现在和Linq结合之后非常方便 .AsParallel()
就OK了
说很抽象看个简单案例:
static void Main(string[] args){ IEnumerable<int> list = new List<int>() { 1, 2, 3, 4, 5, 7, 8, 9 }; foreach (var item in ParallelMethod(list)) { Console.WriteLine(item); }}/// <summary>/// 举个例子/// </summary>private static IEnumerable<int> ParallelMethod(IEnumerable<int> list){ return list.AsParallel().Select(x => x * x);}
正常执行的结果应该是:
1492564164981
并行之后就是这样了(不管顺序了):
2564194981416
当然了,如果你就是对顺序有要求可以使用: .AsOrdered()
/// <summary>/// 举个例子/// </summary>private static IEnumerable<int> ParallelMethod(IEnumerable<int> list){ return list.AsParallel().AsOrdered().Select(x => x * x);}
其实实际项目中,使用并行的时候:任务时间适中,太长不适合,太短也不适合
记得大家在项目里经常会用到如 Sum
, Count
等聚合函数,其实这时候使用并行就很合适
var list = new List<long>();for (long i = 0; i < 1000000; i++){ list.Add(i);}Console.WriteLine(GetSumParallel(list));
private static long GetSumParallel(IEnumerable<long> list){ return list.AsParallel().Sum();}
time dotnet PLINQ.dll
499999500000real 0m0.096suser 0m0.081ssys 0m0.025s
不使用并行:(稍微多了点,CPU越密集差距越大)
499999500000real 0m0.103suser 0m0.092ssys 0m0.021s
其实聚合有一个通用方法,可以支持复杂的聚合:(以上面sum为例)
.Aggregate( seed:0, func:(sum,item)=>sum+item );
稍微扩展一下,PLinq也是支持取消的, .WithCancellation(CancellationToken)
Token的用法和上面一样,就不复述了,如果需要和异步结合,一个 Task.Run
就可以把并行任务交给线程池了
也可以使用Task的异步方法,设置超时时间,这样PLinq超时了也就终止了
PLinq这么方便,其实也是有一些小弊端的,比如它会直接最大程度的占用系统资源,可能会影响其他的任务,而传统的Parallel则会动态调整
任务并行(并行调用)
这个PLinq好像没有对应的方法,有新语法你可以说下,来举个例子:
await Task.Run(() => Parallel.Invoke( () => Task.Delay(TimeSpan.FromSeconds(3)), () => Task.Delay(TimeSpan.FromSeconds(2)) ));
取消也支持:
Parallel.Invoke(new ParallelOptions() { CancellationToken = token }, actions);
扩充说明
其实还有一些比如数据流和响应编程没说,这个之前都是用第三方库,刚才看官网文档,好像已经支持了,所以就不卖弄了,感兴趣的可以去看看,其实项目里面有流数据相关的框架,eg: Spark
,都是比较成熟的解决方案了基本上也不太使用这些了。
然后还有一些没说,比如NetCore里面不可变类型(列表、字典、集合、队列、栈、线程安全字典等等)以及限流、任务调度等,这些关键词我提一下,也方便你去搜索自己学习拓展
先到这吧,其他的自己探索一下吧,最后贴一些Nuget库,你可以针对性的使用:
-
数据流:
Microsoft.Tpl.Dataflow
-
响应编程(Linq的Rx操作):
Rx-Main
-
不可变类型:
Microsoft.Bcl.Immutable
不得不感慨一句,微软妈妈真的花了很多功夫,Net的并发编程比Python省心多了(完)
https://www.cnblogs.com/dotnetcrazy/p/9426279.html
- 老司机教你“飙”EventBus3
- Android listView异步下载和convertView复用产生的错位问题
- 实用Android 屏幕适配方案分享
- java-FFmpeg(一) 实现视频的转码和截图功能
- websocket(二) websocket的简单实现,识别用户属性的群聊
- websocket教程(一) 非常有趣的理解websocket
- 前端插件——头像截图上传插件的使用(带后台)
- 如何减轻ajax定时触发对服务器造成的压力和带宽的压力?ajax-长轮训
- Spark源码之Standalone模式下master持久化引擎讲解
- spring整合quartz
- android沉浸式状态栏的实现
- Jayrock: JSON and JSON-RPC for .NET
- rabbitMQ教程(三) spring整合rabbitMQ代码实例
- 谈谈序列化—实体bean一定要实现Serializable接口?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS 8 安装 MariaDB的详细教程
- Android中RecyclerView拖拽、侧删功能的实现代码
- Android单个RecyclerView实现列表嵌套的效果
- Android如何禁止向EditText控件中输入内容详解
- 小程序视角下同构方案思考
- Android基于自带的DownloadManager实现下载功能示例
- Linux服务器搭建nvidia-docker环境过程详解
- Android开发中libs和jinLibs文件夹的作用详解
- Android多线程之同步锁的使用
- android.graphics.Matrix类用法分析
- 使用VSCode和SSH进行远程开发
- Android利用CountDownTimer实现倒计时功能 Android实现停留5s跳转到登录页面
- Android开发之对话框案例详解(五种对话框)
- Linux下一只五颜六色的「猫」
- Android编程实现自定义ImageView圆图功能的方法