示例代码:https://github.com/lotapp/BaseCode/tree/master/netcore/4_Concurrencygit
先简单说下概念(其实以前也有说,因此简说下):github
Net里面不多用进程,在之前基本上都是线程+池+异步+并行+协程
编程
我这边简单引入一下,毕竟主要是写Python的教程,Net只是帮大家回顾一下,若是你发现还没听过这些概念,或者你的项目中还充斥着各类Thread
和ThreadPool
的话,真的得系统的学习一下了,如今官网的文档已经很完善了,记得早几年啥都没有,也只能挖那些外国开源项目:安全
https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-processing-and-concurrency服务器
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
生效(为了向后兼容)app
对了,若是返回的是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
or task.Result
,这样能够避免死锁Task其余新特征去官网看看吧,引入到此为止了。
这个其实出来好久了,如今基本上都是用PLinq
比较多点,主要就是:
之前都是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); }
正常执行的结果应该是:
1 4 9 25 64 16 49 81
并行以后就是这样了(无论顺序了):
25 64 1 9 49 81 4 16
固然了,若是你就是对顺序有要求可使用:.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
499999500000 real 0m0.096s user 0m0.081s sys 0m0.025s
不使用并行:(稍微多了点,CPU越密集差距越大)
499999500000 real 0m0.103s user 0m0.092s sys 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