• Threads(异步和多线程)


    Task是.NET Framework4.5出现的,线程是基于线程池的,然后提供丰富的api,Thread方法很多很强大,但是太过强大,没有限制。

    DoSomethingLong方法如下:

     /// <summary>
     /// 一个比较耗时耗资源的私有方法
     /// </summary>
     /// <param name="name"></param>
     private void DoSomethingLong(string name)
     {
         Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
         long lResult = 0;
         for (int i = 0; i < 1_000_000_000; i++)
         {
             lResult += i;
         }
         Thread.Sleep(2000);
    
         Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
     }
    View Code

    Task的使用:

    {
        Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1"));
        task.Start();
    }
    {
        Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2"));
    }
    {
        TaskFactory taskFactory = Task.Factory;
        Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3"));
    }

     如果这样去调用:

    ThreadPool.SetMaxThreads(8, 8);
    for (int i = 0; i < 100; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(2000);
        });
    }

     

    如果去掉设置最大线程的代码:

    for (int i = 0; i < 100; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(2000);
        });
    }

    运行结果如下:

     

     ThreadPool.SetMaxThreads(8, 8);

      线程池是单例的,全局唯一的,设置后,同时并发的Task只有8个,而且是复用的,Task的线程是源于线程池的,全局的,请不要这样设置。

    假如我想控制下Task的并发数量,改怎么做?

    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Console.WriteLine("在Sleep之前");
        Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续
        Console.WriteLine("在Sleep之后");
        stopwatch.Stop();
        Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
    }
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Console.WriteLine("在Delay之前");
        Task task = Task.Delay(2000)
            .ContinueWith(t =>
            {
                stopwatch.Stop();
                Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
    
                Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            });//异步等待--等待2s后启动新任务
        Console.WriteLine("在Delay之后");
        stopwatch.Stop();
        Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
    }

    运行结果如下:

      

     如果将最后一个stopwatch注释掉:

     {
         Stopwatch stopwatch = new Stopwatch();
         stopwatch.Start();
         Console.WriteLine("在Sleep之前");
         Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续
         Console.WriteLine("在Sleep之后");
         stopwatch.Stop();
         Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
     }
     {
         Stopwatch stopwatch = new Stopwatch();
         stopwatch.Start();
         Console.WriteLine("在Delay之前");
         Task task = Task.Delay(2000)
             .ContinueWith(t =>
             {
                 stopwatch.Stop();
                 Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
    
                 Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
             });//异步等待--等待2s后启动新任务
         Console.WriteLine("在Delay之后");
         //stopwatch.Stop();
         //Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
     }

     

    什么时候用多线程?

      任务并发是时候

    多线程能干嘛?

      提升速度,优化用户体验。

    比如,现在有一个场景,在公司开会,领导在分配任务,不能并发,因为只能有一个领导在讲话分配任务,当任务分配下去,开发们确实可以同时开始撸代码,这个是可以并发的。

     TaskFactory taskFactory = new TaskFactory();
     List<Task> taskList = new List<Task>();
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", "  DBA ")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat")));

     现在要求,谁第一个完成,获得红包奖励(ContinueWhenAny);所有完成后,一起庆祝下(ContinueWhenAll),将其放入一个List<Task>里面去

     TaskFactory taskFactory = new TaskFactory();
     List<Task> taskList = new List<Task>();
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", "  DBA ")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService")));
     taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat")));
    
     //谁第一个完成,获取一个红包奖励
     taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
     //项目完成后,一起庆祝一下
     taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));

    ContinueWhenAny  ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程

    //阻塞当前线程,等着任意一个任务完成
    Task.WaitAny(taskList.ToArray());//也可以限时等待
    Console.WriteLine("准备环境开始部署");
    //需要能够等待全部线程完成任务再继续  阻塞当前线程,等着全部任务完成
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine("5个模块全部完成后,集中点评");

       Task.WaitAny  WaitAll都是阻塞当前线程,等任务完成后执行操作,阻塞卡界面,是为了并发以及顺序控制,网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll,列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了。

     假如说我想控制下Task的并发数量,该怎么做?  20个

     List<Task> taskList = new List<Task>();
     for (int i = 0; i < 10000; i++)
     {
         int k = i;
         if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20)
         {
             Task.WaitAny(taskList.ToArray());
             taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
         }
         taskList.Add(Task.Run(() =>
         {
             Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
             Thread.Sleep(2000);
         }));
     }

    Parallel并发执行多个Action线程,主线程会参与计算---阻塞界面。等于TaskWaitAll+主线程计算

     Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"),
         () => this.DoSomethingLong("btnParallel_Click_2"),
         () => this.DoSomethingLong("btnParallel_Click_3"),
         () => this.DoSomethingLong("btnParallel_Click_4"),
         () => this.DoSomethingLong("btnParallel_Click_5"));
    Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
    Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
    
    ParallelOptions options = new ParallelOptions();
    options.MaxDegreeOfParallelism = 3;
    Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));

    有没有办法不阻塞?

    Task.Run(() =>
    {
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = 3;
        Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
    });

      几乎90%以上的多线程场景,以及顺序控制,以上的Task的方法就可以完成,如果你的多线程场景太复杂搞不定,那么请梳理一下你的流程,简化一下。建议最好不要线程嵌套线程,两三次勉强能懂,三层就hold不住了,更多的只能求神。

    多线程异常:

    try
    {
    
        List<Task> taskList = new List<Task>();
        for (int i = 0; i < 100; i++)
        {
            string name = $"btnThreadCore_Click_{i}";
            taskList.Add(Task.Run(() =>
            {
                if (name.Equals("btnThreadCore_Click_11"))
                {
                    throw new Exception("btnThreadCore_Click_11异常");
                }
                else if (name.Equals("btnThreadCore_Click_12"))
                {
                    throw new Exception("btnThreadCore_Click_12异常");
                }
                else if (name.Equals("btnThreadCore_Click_38"))
                {
                    throw new Exception("btnThreadCore_Click_38异常");
                }
                Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }));
        }
        //多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
        //那线程异常哪里去了? 被吞了,
        //假如我想获取异常信息,还需要通知别的线程
        Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
    }
    catch (AggregateException aex)//2 需要try-catch-AggregateException
    {
        foreach (var exception in aex.InnerExceptions)
        {
            Console.WriteLine(exception.Message);
        }
    }
    catch (Exception ex)//可以多catch  先具体再全部
    {
        Console.WriteLine(ex);
    }
    //线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
    //工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作

    线程取消:

                    //多线程并发任务,某个失败后,希望通知别的线程,都停下来,how?
                    //Thread.Abort--终止线程;向当前线程抛一个异常然后终结任务;线程属于OS资源,可能不会立即停下来
                    //Task不能外部终止任务,只能自己终止自己(上帝才能打败自己)
    
                    //cts有个bool属性IsCancellationRequested 初始化是false
                    //调用Cancel方法后变成true(不能再变回去),可以重复cancel
                    try
                    {
                        CancellationTokenSource cts = new CancellationTokenSource();
                        List<Task> taskList = new List<Task>();
                        for (int i = 0; i < 50; i++)
                        {
                            string name = $"btnThreadCore_Click_{i}";
                            taskList.Add(Task.Run(() =>
                            {
                                try
                                {
                                    if (!cts.IsCancellationRequested)
                                        Console.WriteLine($"This is {name} 开始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    
                                    Thread.Sleep(new Random().Next(50, 100));
    
                                    if (name.Equals("btnThreadCore_Click_11"))
                                    {
                                        throw new Exception("btnThreadCore_Click_11异常");
                                    }
                                    else if (name.Equals("btnThreadCore_Click_12"))
                                    {
                                        throw new Exception("btnThreadCore_Click_12异常");
                                    }
                                    else if (name.Equals("btnThreadCore_Click_13"))
                                    {
                                        cts.Cancel();
                                    }
                                    if (!cts.IsCancellationRequested)
                                    {
                                        Console.WriteLine($"This is {name}成功结束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                    }
                                    else
                                    {
                                        Console.WriteLine($"This is {name}中途停止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                        return;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine(ex.Message);
                                    cts.Cancel();
                                }
                            }, cts.Token));
                        }
                        //1 准备cts  2 try-catch-cancel  3 Action要随时判断IsCancellationRequested
                        //尽快停止,肯定有延迟,在判断环节才会结束
    
                        Task.WaitAll(taskList.ToArray());
                        //如果线程还没启动,能不能就别启动了?
                        //1 启动线程传递Token  2 异常抓取  
                        //在Cancel时还没有启动的任务,就不启动了;也是抛异常,cts.Token.ThrowIfCancellationRequested
                    }
                    catch (AggregateException aex)
                    {
                        foreach (var exception in aex.InnerExceptions)
                        {
                            Console.WriteLine(exception.Message);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
    View Code

    临时变量:

     for (int i = 0; i < 5; i++)
     {
         Task.Run(() =>
         {
             Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         });
     }

     为什么运行结果后,都是5呢?

      临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候,i已经是5了

    那么该如何解决呢?

      每次都声明一个变量k去接收,k是闭包里面的变量,每次循环都有一个独立的k,5个k变量  1个i变量

    for (int i = 0; i < 5; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        });
    }

    这样再运行,结果就正常了。

     线程安全&lock:

    线程安全:如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的

    线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改

    发生是因为多个线程相同操作,出现了覆盖,怎么解决?

    1 Lock解决多线程冲突

    Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着

    推荐锁是private static readonly object,

     A不能是Null,可以编译不能运行;

    B 不推荐lock(this),外面如果也要用实例,就冲突了

    //Test test = new Test();
    //Task.Delay(1000).ContinueWith(t =>
    //{
    //    lock (test)
    //    {
    //        Console.WriteLine("*********Start**********");
    //        Thread.Sleep(5000);
    //        Console.WriteLine("*********End**********");
    //    }
    //});
    //test.DoTest();
    
    //C 不应该是string; string在内存分配上是重用的,会冲突
    //D Lock里面的代码不要太多,这里是单线程的
    Test test = new Test();
    string student = "水煮鱼";
    Task.Delay(1000).ContinueWith(t =>
    {
        lock (student)
        {
            Console.WriteLine("*********Start**********");
            Thread.Sleep(5000);
            Console.WriteLine("*********End**********");
        }
    });
    test.DoTestString();
    //2 线程安全集合
    //System.Collections.Concurrent.ConcurrentQueue<int>
    
    //3 数据分拆,避免多线程操作同一个数据;又安全又高效
    
    for (int i = 0; i < 10000; i++)
    {
        this.iNumSync++;
    }
    for (int i = 0; i < 10000; i++)
    {
        Task.Run(() =>
        {
            lock (Form_Lock)//任意时刻只有一个线程能进入方法块儿,这不就变成了单线程
            {
                this.iNumAsync++;
            }
        });
    }
    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        Task.Run(() => this.iListAsync.Add(k));
    }
    
    Thread.Sleep(5 * 1000);
    Console.WriteLine($"iNumSync={this.iNumSync} iNumAsync={this.iNumAsync} listNum={this.iListAsync.Count}");
    //iNumSync 和  iNumAsync分别是多少   9981/9988  1到10000以内
  • 相关阅读:
    每天一篇经济学人 2020-09-15 分享在 特朗普的“疫苗政治”:科学与政治之争 | 经济学人
    english notes
    new word
    gilbert strang
    news etc
    对自己的要求
    JDBC添加数据
    题目-1031-字符串反转
    题目-1002-字符串分类统计
    ERROR:格式化hdfs后,datanode只启动了一个
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/11559834.html
Copyright © 2020-2023  润新知