• 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个并发访问一个数据的时候,分拆成多个小数据,避免多个线程去操作同一数据 安全又高效率 大型项目适用

  • 相关阅读:
    struts2 类型转化(typeConverter)
    appfuse-maven-plugin(AMP)
    多项式求和,素数判定 HDU2011.2012
    A+B problems
    黑马程序员----java基础笔记中(毕向东)
    2015Web前端攻城之路
    黑马程序员----java基础笔记上(毕向东)
    黑马程序员----2015黑马之路-启程
    乱弹琴20140421
    读Thinking in java 4
  • 原文地址:https://www.cnblogs.com/LZXX/p/13087088.html
Copyright © 2020-2023  润新知