6语法基础_多线程

基础概念:

进程:计算机概念,一个程序在运行的时候所占据的资源,就像qq一样就是一个进程,而多开QQ,就是多开进程 
线程:计算机概念, QQ,里面的各种聊天 其实就是新开的一个线程
进程和线程:线程属于进程,进程销毁了,线程也就没了,qq关闭了,聊天窗口也就没有了
句柄:描述程序中的某一个最小单元,是一个long数字,操作系统通过这个数字识别应用程序。 
多线程:计算概念,就是某一个进程中,多个线程同时运行;

1.请问异步和多线程的区别

   多线程就是实现异步的方法  异步只是一个概念 。所以一般说到异步:就必然需要从主线程之外开启一个新的线程去执行别的方法

2.什么是异步回调:

  就是发起请求后,不等待响应就去先干别的事  我煮饭后,不等饭煮完,先去偷吃菜

3.为什么计算机可以多线程

  因为有多个CPU的关系就可以同时进行计算 

  单个线程其实可以模拟多线程,循环去执行分片,但是某一个进程卡死了 那么循环就会卡死

4.为什么异步方法会比同步方法快

  同步方法 :CPU利用率低  耗时长 所以性能会慢

       异步方法:CPU利用率低高 耗时短 所以性能会快 但是这是用资源换性能  同时因为异步是需要多线程去执行的,线程调度策略也是需要耗费资源的

 5.为什么线程启动没有顺序,

  因为线程是向操作系统申请的 由操作系统调度策略来决定的 ,但是可以通过BeginInvoke等一些回调 等待来进行顺序控制

6.Task和Thread的区别

      Thread           .net 1.0出现 功能强大,但是开发者用不好 反而会影响代码,没有控制线程数量 线程数量=计算机资源

        ThreadPool    .net 2.0出现,  池化思想: 如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子 保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子;(享元模式) 节约资源提升性能;此外,还能管控总数量,防止滥用; 循环利用,节能减排

      Task  .net 3.0出现, .net多线程最佳实现

7.什么时候需要使用多线程

   需要任务并发的时候  以资源换性能,不卡界面 改善用户体验

8.多线程最好不要断点调试,用输出的结果+线程ID来判断

9.线程安全原理本质上就是排他锁的实现

异步方法

委托方法+BeginInvoke开启一个异步方法

    private void btnAsync_Click(object sender, EventArgs e)
        {
            Action<string> action = this.DoSomethingLong;
            for (int i = 0; i < 5; i++)
            {
                action.BeginInvoke("btnAsync_Click", null, null);
            }
          }

        private void DoSomethingLong(string name)
        {
            Thread.Sleep(2000);//线程等待
        }
View Code

委托方法+BeginInvoke开启一个异步方法 并且有回调

 //异步结果
            IAsyncResult asyncResult = null;

            //回调方法
            AsyncCallback callback = Callback =>
            {
                Thread.Sleep(5000);
                Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}");
       
            };
         
           Action<string> action = this.DoSomethingLong;
           asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数");
View Code

委托方法+BeginInvoke+IsCompleted开启一个异步方法 并且有回调,等到异步完成,如需要进度条显示

  //异步结果
            IAsyncResult asyncResult = null;

            //回调方法
            AsyncCallback callback = Callback =>
            {
                Thread.Sleep(5000);
                Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}");
       
            };
         
           Action<string> action = this.DoSomethingLong;
           asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数");
          

            //2、IsCompleted 完成等待 
            {
                int i = 0;
                while (!asyncResult.IsCompleted)
                {
                    if (i < 9)
                    {
                        Console.WriteLine($"正在玩命为你加载中。。。已经完成{++i * 10}%");
                    }
                    else
                    {
                        Console.WriteLine($"正在玩命为你加载中。。。已经完成99.9999%");
                    }
                    Thread.Sleep(200);
                }

                Console.WriteLine("加载完成。。。");
            }
View Code

委托方法+BeginInvoke+WaitOne 等待异步方法完成

  //异步结果
            IAsyncResult asyncResult = null;

            //回调方法
            AsyncCallback callback = Callback =>
            {
                Thread.Sleep(5000);
                Console.WriteLine($"这里是beginInvoke的第三个参数{Callback.AsyncState}");
       
            };
         
           Action<string> action = this.DoSomethingLong;
           asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "自定义参数");
          
        
            //WaitOne等待
            //asyncResult.AsyncWaitHandle.WaitOne();//一直等待任务完成
            //asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成
            //asyncResult.AsyncWaitHandle.WaitOne(3000);//最多等待3000ms,如果超时了,就不等待了 
View Code

委托方法+BeginInvoke+EndInvoke 等待异步方法完成 并且获取委托方法的返回值

   Func<int> func = () =>
                {
                    //Thread.Sleep(5000);
                    return DateTime.Now.Year;
                };
                func.Invoke();
                IAsyncResult asyncResult1 = func.BeginInvoke(ar =>
                  {
                      func.EndInvoke(ar);
                  }, null);

                int iResult = func.EndInvoke(asyncResult1);
                Console.WriteLine(iResult);
View Code

 

 Thread

Thread.Start()开启新线程的第一种方法

                Thread thread = new Thread(threadStart);
                thread.Start(); //开启一个新线程  
                thread.Suspend();// 暂停线程
                thread.Resume();//恢复  无法实时的去暂停或者恢复线程 
                thread.Abort();//终结线程
                Thread.ResetAbort();//都会有延时
View Code

Thread 等待线程,设置前后台线程 设置线程优先的概率

    thread.Join();//可以限时等待
            thread.Join(2000); //可以限时等待
            
            //设置线程的优先概率,不一定百分百保证先执行
            thread.Priority = ThreadPriority.Highest;

             thread.IsBackground = true;//为后台线程  进程结束,线程结束了
            thread.IsBackground = false; //前台线程   进程结束后,任务执行完毕以后,线程才结束 
View Code

Thread+Fun<> 获取返回结果

 {
                //Thread开启一个新的线程执行任务,如何获取返回结果:

                //定义一个 int返回值的委托
                Func<int> func = () =>
                {
                    Thread.Sleep(5000);
                    return DateTime.Now.Year;
                };
                Func<int> FuncResult = this.ThreadWithReturn(func);//开启线程执行委托方法
                int iResult = FuncResult.Invoke();//如果需要得到执行结果,是必须要等待的

            }



  private Func<T> ThreadWithReturn<T>(Func<T> func)
        {
            T t = default(T);
            ThreadStart threadStart = new ThreadStart(() =>
            {
                t = func.Invoke();
            });
            Thread thread = new Thread(threadStart);
            thread.Start();

            return new Func<T>(() =>
            {
                thread.Join();
                return t;
            });
        }
View Code

 

 ThreadPool 线程池

线程池是全局,Task,async/awit 都是来自于线程池 不轻易设置,线程池里面的线程是有调度策略的,所以一般不去进行设置线程池里面的线程数量

ThreadPool.QueueUserWorkItem 开启一个线程

 ThreadPool.QueueUserWorkItem(o =>
                {
                    Console.WriteLine($"**************** {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                    this.DoSomethingLong("ThreadPool.QueueUserWorkItem1");//开启了一个线程
                }); 
View Code

ThreadPool.QueueUserWorkItem  开启一个线程 传入参数

 ThreadPool.QueueUserWorkItem(o =>
                {
                    Console.WriteLine($"第二个参数:{o}");
                    this.DoSomethingLong("ThreadPool.QueueUserWorkItem1");//开启了一个线程
                }, "参数");
View Code

 

Task

Task开启线程的三种方式

   Console.WriteLine("主线程开始");
                Task task = new Task(() => { Console.WriteLine("开启一个新线程"); });
                task.Start();

                Task.Run(() => { Console.WriteLine("开启第二个新线程"); });

                Task.Factory.StartNew(() => { Console.WriteLine("开启第三个新线程"); });
View Code

卡界面 waitall:阻塞主线程,要等所有线程完成才往下执行 

卡界面 waitany:等待某一个线程完成了,那么就会继续往下走 应用场景: 一个数据需要从 缓存 数据库 api获取 可以开启三个线程去 谁先获取到 就用谁

  List<Task> tasksList = new List<Task>();
                tasksList.Add(Task.Run(() => { Console.WriteLine("开启第1个新线程"); }));
                tasksList.Add(Task.Run(() => { Console.WriteLine("开启第2个新线程"); }));
                tasksList.Add(Task.Run(() => { Console.WriteLine("开启第3个新线程"); }));

                Task[] tasks1 = new Task[0];
                tasks1[0] = Task.Run(() => { Console.WriteLine("开启第1个新线程"); });

                //waitall:阻塞主线程,要等所有线程完成才往下执行
                Task.WaitAll(tasksList.ToArray());//要获取的是一个task数组
                Task.WaitAll(tasks1);//要获取的是一个task数组

                //waitany:等待某一个线程完成了,那么就会往下走下去去
                Task.WaitAny(tasksList.ToArray());//要获取的是一个task数组
                Task.WaitAny(tasks1);//要获取的是一个task数组
View Code

 ContinueWhenAny  ContinueWhenAll 非阻塞式的回调;使用的线程可能是新线程,也可能是刚从线程池回收完毕的线程,唯一不可能就是去使用主线程

            Console.WriteLine("开始线程");
            Console.WriteLine("开始线程");
        

            TaskFactory taskFactory = new TaskFactory();
            List<Task> taskList = new List<Task>();
            taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程1")));
            taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程2")));
            taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程3")));
            taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程4")));
            taskList.Add(taskFactory.StartNew(() => Console.WriteLine("子线程5")));

           

            //找到子线程最先完成的 
            taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"最先完成的子线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
          
            //所有子线程都完成了
            taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
View Code

Task.Delay 延迟执行

   // Task.Delay 出现于4.5版本
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Task task = Task.Delay(2000).ContinueWith(t =>  //任务在2000ms 以后执行
                {
                    stopwatch.Stop();
                    Console.WriteLine($"{stopwatch.ElapsedMilliseconds}");
                    Console.WriteLine("回调已完成");
                            });
                        }
                    }
View Code

Task 获取返回值 会卡界面

 //如果获取返回值 
                Task<int> result = Task.Run<int>(() =>
                {
                    Thread.Sleep(2000);
                    return DateTime.Now.Year;
                });
                int iResult = result.Result; //会卡界面等待
View Code

又想获取异步回调结果,又不想卡界面,可以如下操作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {

            //有想要异步回调获取返回值 又不卡界面 只能花样实现
            Console.WriteLine($"1:{DateTime.Now}");
            Func<int> func = () =>
            {
                Console.WriteLine($"2:{DateTime.Now}");
                Thread.Sleep(3000);
                Console.WriteLine($"3:{DateTime.Now}");
                return DateTime.Now.Year;
            };
            Console.WriteLine($"4:{DateTime.Now}");
            Func<int> func1Rusult = ThreadWithReturen<int>(func); //不卡界面 
            {
                Console.WriteLine($"5:{DateTime.Now}");
                Console.WriteLine("假如这里的执行也需要3秒钟.");
            }
            Console.WriteLine($"6:{DateTime.Now}上面都不会卡界面");
       
            {

                Console.WriteLine($"所以可以在这里做点操作 做一些主线程耗时的运动,");


            }
           

            int iRsult = func1Rusult.Invoke(); //这里会卡界面 最终其实还是会卡界面的 只不过让卡界面放到后面来执行,
            Console.WriteLine($"7:{DateTime.Now}");
        }

        /// <summary>
        /// 一个新的线程来执行委托里的动作
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="func"></param>
        /// <returns></returns>
        private static Func<T> ThreadWithReturen<T>(Func<T> func)
        {
            Console.WriteLine($"8:{DateTime.Now}");
            T t = default(T);
            ThreadStart threadStart = () =>
            {

                Console.WriteLine($"9:{DateTime.Now}");
                t = func.Invoke();
            };
            Console.WriteLine($"11:{DateTime.Now}");
            Thread thread = new Thread(threadStart);
            thread.Start();//不卡界面
            Console.WriteLine($"12:{DateTime.Now}");
            return new Func<T>(() =>
            {
                Console.WriteLine($"13:{DateTime.Now}");
                thread.Join();
                Console.WriteLine($"14:{DateTime.Now}");
                return t;
            });
        }
    }
}
View Code

Task.run.ContinueWith 线程完成后再开启一个延续任务

Task.Run<int>(() =>
                 {
                     Thread.Sleep(2000);
                     return DateTime.Now.Year;
                 }).ContinueWith(intT => //开启一个延续任务
                 {
                     int i = intT.Result;
                 });
View Code

 

Parallel 对Task进一步进行了封装 .Netframework 4.5版本出来

1.Parallel主线程会参与计算 所以会卡顿界面,一般工作中就是使用它控制一下线程数量

2.Task.waitAll+主线程参与计算=Parallel

3.并发执行action,所以它也是多线程

  {
                //Parallel 主要可以控制线程数量
                //Parallel并发执行了五个委托,开启了新线程,主线程参与计算,界面会阻塞
                // Task WaitAll + 主线程
                Parallel.Invoke(() => { this.DoSomethingLong("btnParallel_Click_1"); },
                    () => { this.DoSomethingLong("参数"); },
                    () => { this.DoSomethingLong("参数"); },
                    () => { this.DoSomethingLong("参数"); },
                    () => { this.DoSomethingLong("参数"); });
            }
            {
                ParallelOptions parallelOptions = new ParallelOptions();
                parallelOptions.MaxDegreeOfParallelism = 2;//设置线程最大并发数量为两个
                //for循环十个委托方法,每次只会用两个线程跑
                Parallel.For(0, 10, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}"));
            }
            {
                ////控制线程数量
                ParallelOptions parallelOptions = new ParallelOptions();
                parallelOptions.MaxDegreeOfParallelism = 2;  //控制线程的最大数量
                //控制执行数量
                Parallel.ForEach(new int[] { 12, 13, 14, 15, 16, 17 }, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}"));
            }
View Code

 Parallel 怎么让主线程不参与计算 并且设置最大线程数量

   //如何使主线程不参与计算 就是用子线程包一层
                Task.Run(() =>
                {
                    ParallelOptions parallelOptions = new ParallelOptions();
                    parallelOptions.MaxDegreeOfParallelism = 5;
                    Parallel.For(0, 10, parallelOptions, i => Console.WriteLine("1111111"));
                });
View Code

 

 

await/async

1.await/async 是语法糖,成对出现 要使用的话 是需要.net fromworkd 4.5以上的版本

一定要懂 await/async执行顺序(需要去解读async执行的源码,本质就是状态机+线程上下文来控制的)

2.方法运行的时候 只要遇到 await 就会直接返回去执行主线程的方法

  所以和同步执行没区别,都是串行化执行(串行化意味着不能接受并发请求,要一个个等待执行,就不能提高性能),,但是可以提高吞吐量(响应能力,1秒能处理多少个请求--web模式)

3.await执行后_继续执行的代码都是由主线程来执行

4.await 开启一个新线程执行里面一个方法  await后面的方法相当于异步回调一样

5.有了task那么async的价值在哪里, 为什么要推荐使用async 而不是task?

   async比Task。性能方面并不会提升 该是执行多少秒就是多少秒  但是每秒钟的时候 task可以有1000并发, async比task多个几百

  Task: 同时十个人访问一个资源,task是需要开启十个线程,当需要结果返回的时候Task是需要一个线程去等待结果

  async/await:同时十个人访问一个资源 ,async不一定会开启十个线程,主线程走下去遇到await就返回,而await之后需要执行的可能是主线程和子线程1,2,或者子线程2执行完了,又让子线程2执行:相当于遇到await直接发送命令,并且不需要消费一个线程去等待这个结果-结果完成之前就不浪费CPU资源。完成后才用线程来处理,而且还能重用一些被回收了的线程。所以就能提高吞吐量   长时间需要等待的结果,交给硬件,而不需要CPU参与 async使用的就是硬件DMA技术,所以才可以直接发命令

语法糖,同步方式写异步,增加系统吞吐量,要用就从头到底都用async,web开发推荐

 

适合场景:跟第三方交互的(非托管资源,大多数就有async版本) 

数据库 openasync-redis。webapi-请求,文件读取

不适合场景 服务器本地计算(CPU密集型,托管资源,不管怎么样CPu都要干的活,async就无法偷懒,发再多的命令过去,CPU都是要执行这么久,反而async状态机还会一直切换)用了也没事,因为async很成熟,大数据加减乘数,数据处理

  

 

下面执行方法输出是 1 3 2 4 5 6

  private async static Task Test()
        {
          
            {
                Console.WriteLine($"1  主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Method();
                Console.WriteLine($"2  主线程id={Thread.CurrentThread.ManagedThreadId}");
            }
        
            Console.Read();
        }

    
        /// </summary>
        private static async void Method()
        {
            //主线程执行
            Console.WriteLine($"3  主线程ID={Thread.CurrentThread.ManagedThreadId}");
            Task task = Task.Run(() =>//启动新线程完成任务
            {
                Console.WriteLine($"4,子线程ID={Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(1000);
                Console.WriteLine($"5  子线程ID={Thread.CurrentThread.ManagedThreadId}");
            });
            await task;
            //主线程执行
            Console.WriteLine($"6  子线程ID={Thread.CurrentThread.ManagedThreadId}");
        }
async/await 简单列子
  Task t = NoReturnTask();
                Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                t.Wait();//主线程等待Task的完成  阻塞的
                await t;//await后的代码会由线程池的线程执行  非阻塞



    /// <summary>
        /// 无返回值  async Task == async void
        /// Task和Task<T>能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行
        /// </summary>
        /// <returns></returns>
        private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
        {
            //这里还是主线程的id
            Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");

            Task task = Task.Run(() =>
            {
                Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(1000);
                Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            });
            await task;
            Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
            //return;
            //return new TaskFactory().StartNew(() => { });  //不能return  没有async才行
        }
await/async+阻塞与非阻塞
  //如果要得到返回值就必须要等待的
                Task<long> t = SumAsync();
                Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                long lResult = t.Result;//访问result   主线程等待所有的任务完成 //如果访问Result,就相当于是同步方法!
                t.Wait();//等价于上一行 


 /// <summary>
        /// 带返回值的Task  
        /// 要使用返回值就一定要等子线程计算完毕
        /// </summary>
        /// <returns>async 就只返回long</returns>
        private static async Task<long> SumAsync()
        {
            Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            long result = 0;

            await Task.Run(() =>
            {
                for (int k = 0; k < 10; k++)
                {
                    Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000);
                }
                for (long i = 0; i < 999_999_999; i++)
                {
                    result += i;
                }
            });

          
            Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

            return result;
        }
await/async+带返回值
   Task<int> t = SumFactory();
                Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                long lResult = t.Result;//没有await和async 普通的task
                t.Wait();

   /// 要使用返回值就一定要等子线程计算完毕
        /// </summary>
        /// <returns>没有async Task</returns>
        private static Task<int> SumFactory()
        {
            Console.WriteLine($"SumFactory 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            TaskFactory taskFactory = new TaskFactory();
            Task<int> iResult = taskFactory.StartNew<int>(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine($"SumFactory 123 Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                return 123;
            });
            //Console.WriteLine($"This is {iResult.Result}");
            Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            return iResult;
        }
Task 带返回值

 

多线程异常处理

第一种方法:等待所以异常完成,去捕捉每个异常的线程

 try
                {
                    List<Task> taskList = new List<Task>();
                    for (int i = 0; i < 50; i++)
                    {
                        string name = $"btnThreadCore_Click_{i}";
                        int k = i;
                        taskList.Add(Task.Run(() =>
                        {
                        try
                        {
                            if (k == 5)
                            {
                                throw new Exception($"{name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} 异常了 ");
                            }
                         
                            Console.WriteLine($"this is {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} Ok!");
                            }
                            catch (Exception)
                            {
                                Console.WriteLine("异常");
                            }

                        }));
                    };
                   Task.WaitAll(taskList.ToArray());  //如果这里不等待 try -catch 能否捕捉到异常 ,不能
                    //只有WaitAll 之后才能捕捉到所有的异常信息 
                    //在实际开发中是不允许在子线程中出现异常的
                }
                catch (AggregateException aex)
                {//循环获取具体某个异常线程
                    foreach (var exception in aex.InnerExceptions)
                    {
                        Console.WriteLine(exception.Message);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    throw;
                }
View Code

第二种方法:有异常线程之后,直接取消剩下的线程

      {
                // 1 创建cts 共享变量  2 try-catch 捕捉异常  3 在开启的线程中 判断

                CancellationTokenSource cts = new CancellationTokenSource(); //线程安全 
                //cts 有个状态值 IsCancellationRequested 默认初始化位false 改为True之后不能再改回false 
                //提供一个Cancel() 方法改变IsCancellationRequested状态(不能修改)
                try
                {
                    List<Task> taskList = new List<Task>();
                    for (int i = 0; i < 50; i++)
                    {
                        string name = $"btnThreadCore_Click_{i}";
                        int k = i;
                        Thread.Sleep(new Random().Next(50, 100)); //休息五到十秒 
                        taskList.Add(Task.Run(() =>
                        {
                            try
                            {
                                if (!cts.IsCancellationRequested)
                                {//获取线程是否已经被取消

                                }
                            }
                            catch (Exception ex)
                            {
                                cts.Cancel();//设置 IsCancellationRequested为false
                                Console.WriteLine(ex.Message);
                            }
                           
                      
                        }, cts.Token));// cts.Token让未启动的线程直接取消 
                    };
                    Task.WaitAll(taskList.ToArray());
                }
                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 < 20; i++)   //for 循环很快
            {
                Task.Run(() => // 
                {
                    Console.WriteLine($"btnThreadCore_Click_{i} 线程Id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");

                    //这个输出的i为什么是20? 因为for循环非常快,等循环完成之后线程才开始执行 而且线程还会延迟启动 
                    //所以可能人家循环了4次之后,线程才开启
                });
            }


            //循环里面 开启线程没进行阻塞,代码执行又很快,线程又是延迟启动 所以
            for (int i = 0; i =< 10000; i++)
            {
                int k = i; //输出的 i不一定是10000,一个线程都还没开启 循环就已经完成了
                           //输出的 k 一定是10000  每一次都是一个新的变量 闭包 只对当前作用域代码生效
                Task.Run(() =>
                {
                    Console.WriteLine($"k={k} i={i} 线程Id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                });
            }
View Code

 

多线程安全

多线程为什么会出现线程安全:

在全局变量/共享变量/磁盘文件/静态变量/数据库的值 只要是开启多线程去访问修改的时候,就有可能出现线程安全

 

多线程安全策略 加锁(锁的作用:排他):使用  private static readonly object Obj_Lock = new object();  锁 避免多个线程同时并发使用

 for (int i = 0; i < 100000; i++)
            {
                Task.Run(() =>
                {
                    try
                    {
                    lock (Obj_Lock)//可以 避免多线程并发,如果锁住以后,其实这里跟单线程基本上没啥区别;
                    {
                    this.NumTow += 1;
                    }
                    }
                    catch (Exception)
                    {

                        throw;
                    }

                });
            }
View Code

不要使用 private string Str_Lock = "Richard"; 

当两个String的值同样如: string A1="字母" string A2="字母"  其实它们指向的是同一个对象 

 

现在我们给 A1 A2 加锁 每人5个线程去跑, 当执行A1的时候 A2并不能执行,因为锁住的是同一个对象,

10个线程去执行他们 最终执行的流程是:

A1开启一个新线程去执行,

A2等待A1完成后,A2开启一个先执行,A1又在等待A2

这样一个一个线程去跑 这样既有线程开销,还和单线程一样

所以实际锁住的还是同一个对象 而在实际工作中,String值相同的情况时有发生,而出现问题不容易发现。

以下是string锁例子 其实和没锁一样,

  for (int i = 0; i < 100000; i++)
            {
                this.NumOne += 1;
            }

            for (int i = 0; i < 100000; i++)
            {
                Task.Run(() =>
                {
                    try
                    {
                    lock (Str_Lock)//可以 避免多线程并发,如果锁住以后,其实这里跟单线程基本上没啥区别;
                    {
                    this.NumTow += 1;
                    }
                    }
                    catch (Exception)
                    {

                        throw;
                    }

                });
            }
View Code

 

还有不建议使用This锁,

lock(this)的缺点:

  1:就是在一个线程锁定某对象之后导致整个对象无法被其他线程访问。

  2:This 表示当前的实例 因为通常无法控制的其他人可能会锁定该对象。 所以极大可能造成死锁

  3:任何引用对象的人都可以在对象设计者/创建者不知道它的情况下锁定它。这增加了多线程解决方案的复杂性,并可能影响其正确性。

 

多线程安全策略 线程安全集合 并行操作的集合应运而生了。

 System.Collections.Concurrent.ConcurrentStack 基于线程安全   当我们对应List集合操作的时候 一个读取,一个删除更新,这是不允许的

所以当要使用并发操作一个集合的时候 可以使用 这个线程安全集合是没有加锁的,所以也不会出现死锁的情况

https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent.concurrentstack-1?view=netcore-3.1

 

多线程安全策略  数据分拆,

把10个并发访问一个数据的时候,分拆成多个小数据,避免多个线程去操作同一数据 安全又高效率 大型项目适用

 

posted @ 2020-06-10 17:50  12不懂3  阅读(356)  评论(0编辑  收藏  举报
创作不易,请勿抄袭,欢迎转载!