专栏首页逸鹏说道Python3 与 C# 并发编程之~ 上篇

Python3 与 C# 并发编程之~ 上篇

大家这么急,那就先推Net的,Python过几天再推

其实逆天现在Coding已经是80%变成Python了,20%才是Net,也不确定是否一直在Net界干下去,所以只能尽可能的在说新知识的同时,尽量把脑子里面Net相关的内容教给大家,万一跨行业也算对得起大家的厚爱了(这个我从来不强求,反正什么编程语言都一样,顺其自然~)

NetCore并发编程

示例代码:https://github.com/lotapp/BaseCode/tree/master/netcore/4_Concurrency

先简单说下概念(其实之前也有说,所以简说下):

  1. 并发:同时做多件事情
  2. 多线程:并发的一种形式
  3. 并行处理:多线程的一种(线程池产生的一种并发类型,eg:异步编程
  4. 响应式编程:一种编程模式,对事件进行响应(有点类似于JQ的事件)

Net里面很少用进程,在以前基本上都是 线程+池+异步+并行+协程

我这边简单引入一下,毕竟主要是写Python的教程,Net只是帮你们回顾一下,如果你发现还没听过这些概念,或者你的项目中还充斥着各种 ThreadThreadPool的话,真的得系统的学习一下了,现在官网的文档已经很完善了,记得早几年啥都没有,也只能挖那些外国开源项目:

https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-processing-and-concurrency

1.异步编程(Task)

Task的目的其实就是为了简化 ThreadThreadPool的代码,下面一起看看吧:

异步用起来比较简单,一般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处捕获处理即可

如果 asyncawait就是理解不了的可以这样想: 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";
}

异步这块简单回顾就不说了,留两个扩展,你们自行探讨:

  1. 进度方面的可以使用 IProgress<T>,就当留个作业自己摸索下吧~
  2. 使用了异步之后尽量避免使用 task.Wait or task.Result,这样可以避免死锁

Task其他新特征去官网看看吧,引入到此为止了。


2.并行编程(Parallel)

这个其实出来很久了,现在基本上都是用 PLinq比较多点,主要就是:

  1. 数据并行:重点在处理数据(eg:聚合)
  2. 任务并行:重点在执行任务(每个任务块尽可能独立,越独立效率越高)

数据并行

以前都是 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);
}

其实实际项目中,使用并行的时候:任务时间适中,太长不适合,太短也不适合

记得大家在项目里经常会用到如 SumCount等聚合函数,其实这时候使用并行就很合适

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

文章分享自微信公众号:
我为Net狂

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

如有侵权,请联系 yunjia_community@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • Python3 与 C# 并发编程之~ 进程篇上

    上次说了很多Linux下进程相关知识,这边不再复述,下面来说说Python的并发编程,如有错误欢迎提出~

    逸鹏
  • Python3 与 C# 并发编程之~ 进程篇中

    接着上面继续拓展,补充说说获取函数返回值。 上面是通过成功后的回调函数来获取返回值,这次说说自带的方法:

    逸鹏
  • Python3 与 C# 并发编程之~ 进程篇下

    看看 connection.Pipe方法的定义部分,是不是双向通信就看你是否设置 duplex=True

    逸鹏
  • Python3 与 C# 并发编程之~ 线程篇1

    示例代码:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Thread

    逸鹏
  • Python3 与 C# 并发编程之~ 线程篇2

    其实以前的 Linux中是没有线程这个概念的, Windows程序员经常使用线程,这一看~方便啊,然后可能是当时程序员偷懒了,就把进程模块改了改(这就是为什么之...

    逸鹏
  • Python3 与 C# 并发编程之~ 线程篇3

    在多线程程序中,死锁问题很大一部分是由于线程同时获取多个锁造成的,eg:一个线程获取了第一个锁,然后在获取第二个锁的 时候发生阻塞,那么这个线程就可能阻塞其他线...

    逸鹏
  • Python3 与 C# 并发编程之~ 进程实战篇

    之前说过 Queue:在 Process之间使用没问题,用到 Pool,就使用 Manager().xxx, Value和 Array,就不太一样了:

    逸鹏
  • 13 . Python3之并发编程

    现代的计算机系统主要是由一个或者多个处理器,主存,硬盘,键盘,鼠标,显示器,打印机,网络接口及其他输入输出设备组成。

    常见_youmen
  • C#并发编程之初识并行编程

    之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间。近日,这套系统已有阶段性成果,...

    Enjoy233
  • C#并发编程之初识并行编程

    之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间。近日,这套系统已有阶段性成果,...

    Edison.Ma
  • 并发编程~先导篇上

    并发 :一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

    逸鹏
  • C#并发编程之异步编程(一)

    C#5.0中,对异步编程进行了一次革命性的重构,引入了async和await这两个关键字,使得开发人员在不需要深刻了解异步编程的底层原理,就可以写出十分优美而又...

    Edison.Ma
  • C#并发编程之异步编程(二)

    前面一篇文章介绍了异步编程的基本内容,同时也简要说明了async和await的一些用法。本篇文章将对async和await这两个关键字进行深入探讨,研究其中的运...

    Edison.Ma
  • C#并发编程之异步编程(三)

    本篇是异步编程系列的第三篇,本来计划第三篇的内容是介绍异步编程中常用的几个方法,但是前两篇写出来后,身边的朋友总是会有其他问题,所以决定再续写一篇,作为异步编程...

    Edison.Ma
  • 并发编程之第四篇

    一间大屋子有两个功能 : 睡觉、学习、互不相干 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低 解决方法时准备多个房间(多...

    海仔
  • 1.并发编程~先导篇(上)

    并发 :一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

    逸鹏
  • 并发编程之线程第一篇

    Java虚拟机栈 JVM中由堆、栈、方法区所组成,其中栈内存是给线程使用,每个线程启动后,虚拟机就会为其分配一块栈内存。

    海仔
  • 并发编程之线程第二篇

    这是从Java API层面来描述的 根据Thread.State枚举,分为六种状态

    海仔
  • 并发编程之第三篇(synchronized)

    重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。 自旋重试成功的...

    海仔

扫码关注云+社区

领取腾讯云代金券