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返回值的示例

原文地址:https://www.cnblogs.com/yangmengke2018/p/12237888.html