zoukankan      html  css  js  c++  java
  • C#多线程学习笔记(朝夕eleven) Task启动方式、Task阻塞、Task.Delay()、多线程异常处理、任务取消、多线程的临时变量、共享数据的lock、Task返回值

    1.进程、线程、多线程、计算机概念

      【进程】:一个运行程序       【线程】:依托于进程,一个进程可以包含多个线程

      【多线程】:多个执行流同时运行,主要分为两种:其一CPU运算太快啦,分时间片——上下文切换(加载环境—计算—保存环境)  微观角度讲,一个核同一时刻只能执行一个线程;宏观的讲是多线程并发。其二,多CPU多核,可以独立工作,4核8线程——核指物理的核,线程指虚拟核

      多线程三大特点:不卡主线程、速度快、无序性     【Thread】:C#语言对线程对象的封装

    2.同步与异步      【同步】:完成计算之后,再进入下一行,会阻塞     【异步】:不会等待方法的完成,直接进入下一行,不会阻塞

    3.不同C#版本线程的进化升级

    C#1.0 Thread 线程等待、回调、前后台线程,可以扩展Thread封装回调,各种API函数很多,可设置线程优先级,各种API包括:Start(),Suspend(),Resume(),Abort(),Thread.ResetAbort(),Join(),
    C#2.0 ThreadPool 线程池使用,设置线程池,等待可用ManualResetEvent,砍掉了很多API,可以设置线程池的最大最小数量
    C#3.0 Task  

    4.并发编程

      并发编程包括【多线程】、【并行处理】、【异步编程】、【响应式编程】

    5.Task启动

      5.1为什么要有task

        Task  = Thread + ThreadPool

        Thread:容易造成时间+空间开销,而且使用不当,容易造成线程过多,导致时间片切换...

        ThreadPool:控制能力比较弱,做thread的延续、阻塞、取消、超市等功能较弱,ThreadPool的控制权在CLR而不在编程者...

        Task看起来更像是一个Thread...但是在ThreadPool的基础上进行的封装

        .net4.0之后微软极力推荐使用task进行异步运算

      引用using System.Threading  C#3.0才有、基于ThreadPool

            private void DoSomethingLong(string name)
            {
                Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                long lResult = 0;
                for (int i = 0; i < 1000000000; i++)
                {
                    lResult += i;
                }
                Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
            }
    耗时的方法,无返回值,一个string输入参数
    1 Task.Run(() => this.DoSomethingLong("btnTask_Click1"));
    2 Task.Run(() => this.DoSomethingLong("btnTask_Click2"));
    使用Task Run(Action action)运行一个新线程
    TaskFactory taskFactory = Task.Factory;//4.0
    taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));
    使用TaskFactory.StartNew(Action action)开启一个新线程
    new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();
    直接new并start一个新线程

      以上三种启动方式并无区别,都是返回一个Task

      Run(Action)不具有参数,没有返回值

                Task.Run(() =>
                {
                    string name = "无名方法";
                    Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                    long lResult = 0;
                    for (int i = 0; i < 1000000000; i++)
                    {
                        lResult += i;
                    }
                    Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
                });
    理解Run(Action)
                Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                Task.Run(()=>
                {
                    Console.WriteLine($"这是Tsak.Run(Action)无名方法Start      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                    Random random = new Random();
                    Thread.Sleep(random.Next(1000,5000));
                    Console.WriteLine($"这是Tsak.Run(Action)无名方法End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                });
                Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    理解Task.Run(Action)

     6.多线程阻塞-----Task.WaitAll

      设置一场景,睡觉之后要吃饭

      6.1不阻塞多线程,就会出现还没睡醒就吃饭的情况

                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
                listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
                listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
                Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
            
    不阻塞多线程

        6.2阻塞多线程,使用Task.WaitAll(Task[]),会阻塞当前线程,等着全部任务都完成后,才进入下一行

                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
                listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
                listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
                Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    使用Task.WaitAll(Task[])阻塞多线程

      6.2.1使用Task.WaitAll如果想不阻塞线程,可以使用Task套用Task

                Task.Run(() =>
                {//Task套Task,可以不阻塞
                    List<Task> listTask = new List<Task>();
                    listTask.Add(Task.Run(() => DoSomethingLong("杨三少")));
                    listTask.Add(Task.Run(() => DoSomethingLong("牛大帅")));
                    listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝")));
                    Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                    Console.WriteLine($"全部都醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】  时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                });
    Task套用Task

       6.3设置最多等待时长,设置最长等待时间,可以看到,杨三少还未睡醒就开吃,谁让杨三少睡的太久了呢,哈哈

                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
                listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
                listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
                //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
                Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task.WaitAll(Task[],int MaxTime)

       6.4等待汇总

     7.多线程阻塞-----Task.WaitAny

                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
                listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
                listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
                //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
                Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃”
                Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task.WaitAny(listTask.ToArray())

    “这次是杨三少醒的最早,所以他就可以先吃辣”

     8.多线程不阻塞----Task.WhenAll(Task[]).ContinueWith()     Task.WhenAny(Task[]).ContinueWith() 

                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(()=> DoSomethingLong("杨三少")));
                listTask.Add(Task.Run(() => DoSomethingLong("牛大帅")));
                listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝")));
                //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
                //Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃”
                Task.WhenAny(listTask.ToArray()).ContinueWith(t=>
                { 
                    Console.WriteLine($"第一个已经醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); 
                });
                Task.WhenAll(listTask.ToArray()).ContinueWith(t=>
                {
                    Console.WriteLine($"全部都醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】  时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                });
    Task多线程不阻塞

       ContinueWith类似于一个回调式的,ContinueWith里面的t就是前面的Task,也就是使用ContinueWith相当于一个回调,当执行了这个Task之后会自动执行ContinueWith里的后续方法。

    9.仅用11个线程完成10000个任务

                    List<int> list = new List<int>();
                    for (int i = 0; i < 10000; i++)
                    {
                        list.Add(i);
                    }
                    //完成10000个任务  但是只要11个线程  
                    Action<int> action = i =>
                    {
                        Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"));
                        Thread.Sleep(new Random(i).Next(100, 300));
                    };
                    List<Task> taskList = new List<Task>();
                    foreach (var i in list)
                    {
                        int k = i;
                        taskList.Add(Task.Run(() => action.Invoke(k)));
                        if (taskList.Count > 10)
                        {
                            Task.WaitAny(taskList.ToArray());//只要有一个完成,重新整理集合
                            taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
                        }
                    }
                    Task.WhenAll(taskList.ToArray());
    完成10000个线程仅用10个线程

     10.使用TaskFactory可以知道已完成线程的标识

                    TaskFactory taskFactory = new TaskFactory();
                    List<Task> taskList = new List<Task>();
                    taskList.Add(taskFactory.StartNew(o => DoSomethingLong("一一一"), "一一一"));
                    taskList.Add(taskFactory.StartNew(o => DoSomethingLong("二二二"), "二二二"));
                    taskList.Add(taskFactory.StartNew(o => DoSomethingLong("三三三"), "三三三"));
                    taskFactory.ContinueWhenAny(taskList.ToArray(), t =>
                     {
                         Console.WriteLine($"【{t.AsyncState}】任务已完成  当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                     });
                    taskFactory.ContinueWhenAll(taskList.ToArray(),t=>
                    {
                        Console.WriteLine($"所有任务已完成,第一个是{t[0].AsyncState}   当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    });
    TaskFactory与AsyncState

       如果不用TaskFactory,直接用Task,可以通过做个子类的方式来搞。

    11.小结

    Task.WaitAll(Task[]) 阻塞,等待Task[]数组里的全部线程任务都完成后方可运行到下一步
    Task.WaitAny(Task[]) 阻塞,等待Task[]数组里的某一线程任务完成,方可运行到下一步
    Task.WhenAll(Task[]).Continue 不阻塞,等待Task[]数组里的全部线程任务都完成后执行回调
    Task.WhenAny(Task[]).Continue 不阻塞,等待Task[]数组里的某一线程任务完成后执行回调
       
       

    12.Thread.Sleep(2000)与Task.Delay(2000)

      Thread.Sleep(2000);//等待2s    Task.Delay(2000);//延迟2s

      12.1Thread.Sleep(2000)

                    Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    Thread.Sleep(2000);//等待
                    stopwatch.Stop();
                    Console.WriteLine(stopwatch.ElapsedMilliseconds);
                    Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Thread.Sleep(2000),阻塞当前线程2秒,继续向下执行

       12.2Task.Delay(2000)直接替换Thread.Sleep(2000)

                    Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    Task.Delay(2000);//延迟
                    stopwatch.Stop();
                    Console.WriteLine(stopwatch.ElapsedMilliseconds);
                    Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task.Delay(2000),不会起到等待效果

       12.3Task.Delay(2000)的正确用法

                    Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    Task.Delay(2000).ContinueWith(t =>
                    {//Task.Delay()的返回值仍然是Task,  Task.ContinueWith()
                        stopwatch.Stop();
                        Console.WriteLine(stopwatch.ElapsedMilliseconds);
                    });
                    Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
    Task.Delay(2000).ContinueWith()

     13.多线程中的异常处理

      主线程的异常捕捉不到子线程的异常信息。所以新开辟的子线程要写属于自己的try...catch...

      子线程如果不阻塞,主线程是捕捉不到。但最最好还是在子线程里书写自己的异常捕捉机制。

                Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                List<Task> listTask = new List<Task>();
                try
                {
                    for (int i = 0; i < 5; i++)
                    {
                        Action<string> action = t =>
                          {
                              try
                              {
                                  Thread.Sleep(100);
                                  if (t.Equals("for2"))
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行异常");
                                      throw new Exception($"这是{t},刨出异常");
                                  }
                                  if (t.Equals("for3"))
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行异常");
                                      throw new Exception($"这是{t},刨出异常");
                                  }
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行成功");
                              }
                              catch (Exception ex)
                              {
                                  Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                              }
                          };
                        string k = $"for{i}";
                        listTask.Add(Task.Run(() => action.Invoke(k)));
                    }
                    Task.WaitAll(listTask.ToArray());
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                }
                Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    多线程异常处理方法

      14.线程取消

      应用场景是多个线程并发执行,某个失败后,希望通知别的线程,都停下来;Task是外部无法中止的,Thread.Abort不要考虑因为不靠谱(原因是线程是OS的资源,无法掌控啥时候取消),在这里需要线程自己停止自己,需要用到公共的访问变量,所有的线程都会不断的去访问它,当某个线程执行异常时修改此公共变量(当然这中间有一定的延迟)。

      CancellationTokenSource变量去标志任务是否取消,Cancel()方法用于取消,IsCancellationRequested属性作为每个线程执行的条件;其Token在启动Task的时候传入,如果某一时刻执行Cancel()方法,这个任务会自动放弃,并抛出一个异常。

                Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                List<Task> listTask = new List<Task>();
                CancellationTokenSource cts = new CancellationTokenSource();
                try
                {
                    for (int i = 0; i < 5; i++)
                    {
                        Action<string> action = t =>
                          {
                              try
                              {
                                  Thread.Sleep(100);
                                  if (t.Equals("for2"))
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行失败");
                                      throw new Exception($"这是{t},刨出异常");
                                  }
                                  if (t.Equals("for3"))
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行失败");
                                      throw new Exception($"这是{t},刨出异常");
                                  }
                                  if (cts.IsCancellationRequested)
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  放弃执行");
                                  }
                                  else
                                  {
                                      Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行成功");
                                  }
                              }
                              catch (Exception ex)
                              {
                                  cts.Cancel();
                                  Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                              }
                          };
                        string k = $"for{i}";
                        listTask.Add(Task.Run(() => action.Invoke(k),cts.Token));
                    }
                    Task.WaitAll(listTask.ToArray());
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                }
                Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    CancellationTokenSource应用

      15.多线程中的临时变量

      15.1直接引用for循环中的i

                Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                for (int i = 0; i < 5; i++)
                {
                    Task.Run(() =>
                    {
                        Console.WriteLine($"这是for循环,i={i}");
                    });
                }
                Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    直接引用for循环中的i

       为什么都等于5,线程并没有阻塞,所以5次循环下来之后i就是5啦。

      15.2直接引用循环外的局部变量

                Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                int k = 0;
                for (int i = 0; i < 5; i++)
                {
                    k = i;
                    new Action(() =>
                    {
                        Console.WriteLine($"这是for循环,i={k}");
                    }).BeginInvoke(null, null);
                }
                Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    不用i,直接把k声明在循环外

        为什么都等于4,线程没有阻塞,其实最主要的原因是声明了一次k,全程只有一个k。

      15.3在循环内每次声明赋值

                Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    new Action(() =>
                    {
                        Console.WriteLine($"这是for循环,i={k}");
                    }).BeginInvoke(null, null);
                }
                Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    正确的做法,每次在循环内声明赋值

      16.线程安全

      共有变量指:都能访问的局部变量、全局变量、数据库中的一个值、磁盘文件等,如果多个线程都去操作一个共有变量,可能会出现意外的结果,这就是多线程并不安全

      16.1多线程并不安全

                List<Task> listTask = new List<Task>();
                int sum = 0;
                for (int i = 0; i < 10000; i++)
                {
                    listTask.Add(Task.Run(() =>
                    {
                        sum += 1;
                    }));
                }
                Task.WhenAll(listTask.ToArray()).ContinueWith((t) =>
                {
                    Console.WriteLine($"最终结果:{sum}");
                });
    没有lock

    可见结果并不是意料之中的10000.

      16.2加lock

            //private:定义为私有,防止外界也去lock    static:全场唯一   readonly:不要随意赋值  object表示引用类型
            private static readonly object objLock = new object();
            private void button1_Click(object sender, EventArgs e)
            {
                List<Task> listTask = new List<Task>();
                int sum = 0;
                for (int i = 0; i < 10000; i++)
                {
                    listTask.Add(Task.Run(() =>
                    {
                        lock (objLock)
                        {
                            //lock后的方法块,任意时刻只有一个线程可以进入  
                            //只能锁引用类型,相当于占用这个引用链接   
                            //string虽然也是引用类型,但不能用string,因为什么享元模式
                            sum += 1;
                        }
                    }));
                }
                Task.WhenAll(listTask.ToArray()).ContinueWith((t) =>
                {
                    Console.WriteLine($"最终结果:{sum}");
                });
            }
    加了lock锁

    可见结果是意料之中的10000

      16.3有关lock的深思

      lock虽然能解决线程安全的问题,同一时刻只能有一个线程可以运行lock后的程序块不并发,但是牺牲了性能,所以有两个建议:

      其一、尽可能得缩小lock的范围;其二、尽量不要定义共有变量,可以通过数据拆分来避免冲突

     17.await/async

      await与async通常成对出现,async放在方法名前(private后面),await放在task对象前。如果只在方法名前加一个async,没有任何意义;如果只在方法内task前面加一个await,会报错。在await task后面的语句,类似于一个回调,方法运行到await task时会自动返回返回主线程继续运行,要等待子线程运行结束后才会继续运行这个回调,并且这个回调的线程是不确定的,可能是主线程,可能是子线程,也可能是其他线程。

      不用用void作为async方法的返回类型,如果没有返回值,也要Task,如果有返回值,要返回Task<T>;仅限于编写事件处理程序需要返回void

       17.1类似于用一种同步的方法写异步。举例说在主线程中运行了一个task,并且希望在这个子线程task执行之后再运行另一个事件task1,那么就需要task.ContinueWith(task1),在这里task.ContinueWith(task1)就等于await task;task1,其实说白了就是ContinueWith=await。

            /* 使用swait/async可以把异步当成同步来使用
             * 这个示例演示一个没有返回值的多线程任务,模拟人一天的动作
             * 其执行结果的先后顺序是【按钮单击开始】【NoReturn方法Start】【开始吃饭】【吃饭结束】【开始工作】【工作结束】【工作结束】【开始睡觉】【NoReturn方法End】【按钮单击结束】
             * 这个示例可能并无实际的意义,但只是用来模拟这种await/async的使用方法
             * 不过请注意使用async的方法虽说没有返回值(并没有具体的return task),但其结果返回的是Task
             */
            private async void button1_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"ClickEventStart     【按钮单击开始】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                await NoReturn_AwaitAsync();
                Console.WriteLine($"ClickEventEnd       【按钮单击结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }
    
            private async static Task NoReturn_AwaitAsync()
            {
                Console.WriteLine($"【NoReturn方法Start】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                await Task.Run(()=>
                {
                    Console.WriteLine($"NoReturn方法await之前   【开始吃饭】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(500);
                    Console.WriteLine($"NoReturn方法await之后   【吃饭结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                });
                await Task.Run(() =>
                {
                    Console.WriteLine($"NoReturn方法await之前   【开始工作】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(500);
                    Console.WriteLine($"NoReturn方法await之后   【工作结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                });
                Console.WriteLine($"NoReturn方法await之后   【开始睡觉】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Console.WriteLine($"【NoReturn方法End】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }
    无返回值的await/async使用

      

       同样的方法从await/async换成Task.ContinueWith来一步步回调则显得代码很冗余

            private static void NoReturn_Task_ContinueWith()
            {
                Console.WriteLine($"【NoReturn方法Start】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Task.Run(() =>
                {
                    Console.WriteLine($"NoReturn方法await之前   【开始吃饭】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(500);
                    Console.WriteLine($"NoReturn方法await之后   【吃饭结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                }).ContinueWith(t=>
                {
                    Task.Run(() =>
                    {
                        Console.WriteLine($"NoReturn方法await之前   【开始工作】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                        Thread.Sleep(500);
                        Console.WriteLine($"NoReturn方法await之后   【工作结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    }).ContinueWith(t1=>
                    {
                        Console.WriteLine($"NoReturn方法await之后   【开始睡觉】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                        Console.WriteLine($"【NoReturn方法End】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    });
                });
            }
    使用Task.ContinueWith()代替await/async

      17.2 task有返回值的场景

            private void button1_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"ClickEventStart     【按钮单击开始】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Task<int> task = Task.Run(() => DoSomething("杨三少"));
                int result = task.Result;//这一步子线程会阻塞主线程,因为主线程需要调用子线程的执行结果
                Console.WriteLine($"子线程执行结果为{result}");
                Console.WriteLine($"ClickEventEnd       【按钮单击结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }
    
            private int DoSomething(string name)
            {
                Console.WriteLine($"Task【{name}】 线程ID:{Thread.CurrentThread.ManagedThreadId} 是否在线程池中:{Thread.CurrentThread.IsThreadPoolThread}");
                return 22;
            }
    不实用await/async运行Task返回值的示例

  • 相关阅读:
    【python学习笔记02】python的数据类型2
    元器件选型(一)ESD、TVS参考资料
    【python学习笔记01】python的数据类型
    你不知道的vue生命周期钩子选项(三)
    vue源码解析之选项合并(二)
    vue源码解析之选项合并(一)
    聊聊var与let 在window下面的区别(除开作用域)
    浅谈JavaScript中的防抖和节流
    Redhat6.4下安装Oracle10g
    Ubuntu Server 14.04 集成
  • 原文地址:https://www.cnblogs.com/yangmengke2018/p/12237888.html
Copyright © 2011-2022 走看看