6语法基础_多线程

基础概念:

同步:洗衣服开始-洗衣服结束

异步:洗衣服开始-洗衣服结束  在中间可以煮个饭 这就是异步

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

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

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

异步方法

委托方法+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:等待某一个线程完成了,那么就会往下走下去去

  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

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

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版本出来

Parallel  设置多个线程去执行方法

  {
                //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

await/async

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

一定要懂 await/async执行顺序

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

await 等新线程里面的方法执行完毕 就会执行await后面的方法   await后面的方法相当于异步回调一样

下面执行方法输出的是 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 循环很快
                {//这个线程里面输出的i是20 因为for循环非常快,等循环万之后线程才开始执行
                    Task.Run(() => // 开启线程;不会阻塞的,线程会延迟启动
                    {
                        Console.WriteLine($"btnThreadCore_Click_{i} 线程Id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    });
                }


                for (int i = 0; i < 20; i++)
                {
                    int k = i; //作用域 这个k 是不是只是针对于某一次for 循环,循环20次就会有20 k
                    Task.Run(() =>
                    {
                        Console.WriteLine($"btnThreadCore_Click_{k} 线程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个并发访问一个数据的时候,分拆成多个小数据,避免多个线程去操作同一数据 安全又高效率 大型项目适用

原文地址:https://www.cnblogs.com/LZXX/p/13087088.html