• Python3 与 C# 并发编程之~ 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

  • 相关阅读:
    【整理】数组面试题集锦
    【整理】二叉树面试题集锦
    【转】C++怎么设计只能在堆或者栈分配空间的类以及定义一个不能被继承的类
    【转】面试题:最长回文子串
    【转】后缀数组求最长重复子串
    【转】linux硬链接与软链接
    【转】随机函数面试题
    【转】C++ 重载、覆盖和隐藏
    分类算法评估指标
    Pandas_对某列的内容分列
  • 原文地址:https://www.cnblogs.com/dunitian/p/9419325.html
Copyright © 2020-2023  润新知