基础概念:
同步:洗衣服开始-洗衣服结束
异步:洗衣服开始-洗衣服结束 在中间可以煮个饭 这就是异步
异步和多线程的关系:多线程就是实现异步的方法 异步只是一个概念 所以一般说到异步:就必然需要从主线程之外开启一个新的线程去执行别的方法
异步回调:就是发起请求后,不等待响应就去先干别的事 我煮饭后,不等饭煮完,先去偷吃菜
进程:计算机概念,一个程序在运行的时候所占据的资源,就像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);//线程等待 }
委托方法+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, "自定义参数");
委托方法+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("加载完成。。。"); }
委托方法+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,如果超时了,就不等待了
委托方法+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);
Thread
Thread.Start()开启新线程的第一种方法

Thread thread = new Thread(threadStart); thread.Start(); //开启一个新线程 thread.Suspend();// 暂停线程 thread.Resume();//恢复 无法实时的去暂停或者恢复线程 thread.Abort();//终结线程 Thread.ResetAbort();//都会有延时
Thread 等待线程,设置前后台线程 设置线程优先的概率

thread.Join();//可以限时等待 thread.Join(2000); //可以限时等待 //设置线程的优先概率,不一定百分百保证先执行 thread.Priority = ThreadPriority.Highest; thread.IsBackground = true;//为后台线程 进程结束,线程结束了 thread.IsBackground = false; //前台线程 进程结束后,任务执行完毕以后,线程才结束
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 FuncThreadWithReturn (Func func) { T t = default(T); ThreadStart threadStart = new ThreadStart(() => { t = func.Invoke(); }); Thread thread = new Thread(threadStart); thread.Start(); return new Func (() => { thread.Join(); return t; }); }
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");//开启了一个线程 });
ThreadPool.QueueUserWorkItem 开启一个线程 传入参数

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

Console.WriteLine("主线程开始"); Task task = new Task(() => { Console.WriteLine("开启一个新线程"); }); task.Start(); Task.Run(() => { Console.WriteLine("开启第二个新线程"); }); Task.Factory.StartNew(() => { Console.WriteLine("开启第三个新线程"); });
waitall:阻塞主线程,要等所有线程完成才往下执行
waitany:等待某一个线程完成了,那么就会往下走下去去

ListtasksList = new List (); 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数组
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("回调已完成"); }); } }
Task 获取返回值 会卡界面

//如果获取返回值 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); int iResult = result.Result; //会卡界面等待
Task.run.ContinueWith 线程完成后再开启一个延续任务

Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }).ContinueWith(intT => //开启一个延续任务 { int i = intT.Result; });
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}")); }
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(); } /// 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}"); }

Task t = NoReturnTask(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); t.Wait();//主线程等待Task的完成 阻塞的 await t;//await后的代码会由线程池的线程执行 非阻塞 ////// 无返回值 async Task == async void /// Task和Task 能够使用await, Task.WhenAny, Task.WhenAll等方式组合使用。Async Void 不行 /// /// 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才行 }

//如果要得到返回值就必须要等待的 Task<long> t = SumAsync(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//访问result 主线程等待所有的任务完成 //如果访问Result,就相当于是同步方法! t.Wait();//等价于上一行 ////// 带返回值的Task /// 要使用返回值就一定要等子线程计算完毕 /// /// async 就只返回long 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; }

Task<int> t = SumFactory(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//没有await和async 普通的task t.Wait(); /// 要使用返回值就一定要等子线程计算完毕 /// ///没有async Task 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; }
多线程异常处理
第一种方法:等待所以异常完成,去捕捉每个异常的线程

try { ListtaskList = new List (); 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; }
第二种方法:有异常线程之后,直接取消剩下的线程

{ // 1 创建cts 共享变量 2 try-catch 捕捉异常 3 在开启的线程中 判断 CancellationTokenSource cts = new CancellationTokenSource(); //线程安全 //cts 有个状态值 IsCancellationRequested 默认初始化位false 改为True之后不能再改回false //提供一个Cancel() 方法改变IsCancellationRequested状态(不能修改) try { ListtaskList = new List (); 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); } }
多线程里面的临时变量问题

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")}"); }); }
多线程安全
多线程为什么会出现线程安全:
在全局变量/共享变量/磁盘文件/静态变量/数据库的值 只要是开启多线程去访问修改的时候,就有可能出现线程安全
多线程安全策略 加锁(锁的作用:排他):使用 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; } }); }
不要使用 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; } }); }
还有不建议使用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个并发访问一个数据的时候,分拆成多个小数据,避免多个线程去操作同一数据 安全又高效率 大型项目适用