C# 同步异步与多线程:Thread / Task / 线程池一次讲透(上篇)
C# 同步异步与多线程Thread / Task / 线程池一次讲透上篇程序卡顿、界面无响应几乎都是同步阻塞惹的祸。这课从同步 vs 异步讲起把Thread 多线程、Task 异步、线程池、线程状态、跨线程更新 UI一次讲透再配一个递归遍历文件的实战。下篇讲 IComparer 自定义排序 FileList 文件管理器案例一、同步与异步先搞清调度和等待同步和异步是编程中两种不同的代码执行模式核心区别就是调度和等待的问题同步顺序执行上一个做完才做下一个。如果某个操作很耗时程序就会卡顿、响应变慢异步把耗时任务交给别的线程当前线程不等待继续往下走。同步阻塞的例子privatevoidbutton1_Click(objectsender,EventArgse){MessageBox.Show(按钮1被点击了,开始工作);// 耗时的任务占用内存1 亿次循环for(inti0;i1_0000_0000;i){Console.WriteLine(6666666);}// 因为和上面的 for 在同一个同步代码块中必须等它执行完MessageBox.Show(一天的工作结束了);}特点顺序执行但遇到耗时操作就会卡顿。这是同步最大的痛点。二、用 Thread 解决耗时任务线程就像多请了几个干活的人专门处理耗时任务privatevoidbutton2_Click(objectsender,EventArgse){MessageBox.Show(邵帛点餐);// 线程触发器委托比较耗时的任务ThreadStartstartnewThreadStart(Test);// 用线程处理耗时任务相当于另开辟资源ThreadthreadnewThread(start);thread.Start();// 启动线程MessageBox.Show(张凯点餐);// 这行不会等 Test 执行完}privatevoidTest(){for(inti0;i1_0000_0000;i){Console.WriteLine(袁士博点餐);}}线程的三种开启写法从繁到简// 写法1ThreadStart 委托ThreadStartstartnewThreadStart(TestMethod);Threadt1newThread(start);t1.Start();// 写法2直接传方法t1newThread(TestMethod);t1.Start();// 写法3匿名委托最简单Threadt2newThread(()Console.WriteLine(匿名委托开启分线程));t2.Start();线程里算斐波那契递归Threadt3newThread((){intresFac(5);// 在分线程里算MessageBox.Show(res.ToString());});t3.Start();// 斐波那契数列递归的一种表现// 遇到功能一样、只有数据变化时用自身调用自身来完成业务逻辑privateintFac(intn){if(n1)returnn;returnFac(n-1)Fac(n-2);}三、跨线程更新 UI必须用 Invoke铁律UI 控件只能由创建它的线程UI 线程更新。分线程想改界面必须回到主线程privatevoidbutton4_Click(objectsender,EventArgse){Threadt4newThread((){intresultFac(10);// 不加 Invoke 会报错线程间操作无效从不是创建控件label1的线程访问它Invoke(newAction(()label1.Textresult.ToString()));});t4.Start();}四、线程的停止与状态线程的生命周期管理4 个常用方法t1.Abort();// 抛异常终止线程try-catch 包住t1.Join();// 阻塞当前线程等待它停止t1.Suspend();// 挂起线程达到停止效果t1.Resume();// 跟 Suspend 配套恢复继续Abort 终止 出错重置privatevoidbutton3_Click(objectsender,EventArgse){try{if(t1!null){t1.Abort();// 抛异常终止t1null;}}catch(Exceptionex){MessageBox.Show($线程终止出现了错误:{ex.Message});Thread.ResetAbort();// 终止出错取消掉所有终止}}线程状态ThreadStateThreadt11newThread(PrintNumWidthDelay);Console.WriteLine($t11 的状态为:{t11.ThreadState});// Unstartedt11.Start();// ... 运行中会是 RunningThread.Sleep(TimeSpan.FromSeconds(6));t11.Abort();Console.WriteLine($休眠后 t11 的状态为:{t11.ThreadState});Console.WriteLine(t11.IsAlive);// 是否活着true 表示还活着常见状态Unstarted未启动→Running运行中→Aborted已终止IsAlive判断是否存活。五、线程池把线程放进池子统一管理线程池把多个线程放到一个池子里好处是按顺序、方便管理、提高效率。用ThreadPool.QueueUserWorkItem添加任务// public delegate void WaitCallback(object state);ThreadPool.QueueUserWorkItem((s)Console.WriteLine(${s}),你好);ThreadPool.QueueUserWorkItem((s){for(inti0;i10;i)Console.WriteLine($十个线程任务都放到了池子中{i1});},0);// 比直接 new Thread 要方便用 ManualResetEvent 控制任务顺序场景任务 A 干完任务 B 才能开始发信号。// 重置当前事件相当于准备一个空的事件ManualResetEventresetEventnewManualResetEvent(false);ThreadPool.QueueUserWorkItem(o{for(inti0;i20;i){DoSomething(o.ToString());Thread.Sleep(1);}resetEvent.Set();// 发信号A 干完了},上料);resetEvent.WaitOne();// 阻塞线程直到收到信号才疏通ThreadPool.QueueUserWorkItem(o{for(inti0;i20;i)DoSomething(o.ToString());},检测);ManualResetEvent(false)初始无信号 → 任务完成后Set()发信号 →WaitOne()等待信号放行。这就是让上料完成后再检测的顺序控制。六、Task更高级的异步Task可以理解为结合了 Action 和 Func是对线程池的封装提供异步处理和任务组合能力适合密集型任务。带返回值的 Task ContinueWith链式// 相当于 Func 的 Task返回类型是 intTaskinttnewTaskint(newFuncint((){returnFac(40);// 耗时任务}));// ContinueWith等待上面的异步执行完毕之后才触发避免阻塞当前线程t.ContinueWith(t2{Console.WriteLine($等待上面的任务完成,结果:{t2.Result});});t.Start();Console.WriteLine(我也要执行!!!!!!!!!!);// 不等 t先执行Task.Run 快速开启 WhenAny 竞速Task[]tasksnewTask[]{Task.Run((){Thread.Sleep(1000);Console.WriteLine(点外卖);}),Task.Run((){Thread.Sleep(3000);Console.WriteLine(吃外卖);}),Task.Run((){Thread.Sleep(2000);Console.WriteLine(扔外卖);})};// 任何一个异步执行完毕就进入 continue竞速Task.WhenAny(tasks).ContinueWith(t{Console.WriteLine(任何一个异步执行完毕,就进入 continue);});// 另外两种WhenAll 全部完成 / WaitAny 返回最先完成的索引// int ind Task.WaitAny(tasks);// Task.WhenAll(tasks).ContinueWith(t Console.WriteLine(全部干完了));WhenAny谁先完成谁触发竞速WhenAll全部完成才触发。七、实战递归遍历文件夹下的所有 .txt把递归用在文件系统上——遇文件判断扩展名遇文件夹就递归进入privatevoidFindTxtFiles(stringfolderPath){string[]filePathDirectory.GetFileSystemEntries(folderPath);foreach(variteminfilePath){if(File.Exists(item))// 是文件{if(Path.GetExtension(item).txt)// 是 txtConsole.WriteLine(item);}elseif(Directory.Exists(item))// 是文件夹{// 核心递归进入下一层FindTxtFiles(item);}}}八、上篇小结知识点一句话同步 vs 异步同步顺序执行易卡顿异步不阻塞当前线程Thread多请人干活new Thread((){})最简跨线程 UI必须Invoke否则报线程间操作无效线程停止Abort/Join/Suspend/Resume出错用ResetAbort线程池ThreadPool.QueueUserWorkItem统一管理ManualResetEventSet()发信号WaitOne()等待控制任务顺序Task带返回值 ContinueWith链式WhenAny/WhenAll递归遍历文件系统文件处理、文件夹递归下篇预告多线程搞定了那ListView 文件管理器是怎么实现按名称/日期/类型/大小自定义排序的IComparer接口怎么用多个窗体怎么嵌进一个主界面下篇《C# 自定义排序 IComparer FileList 文件管理器实战》继续点关注别错过参考结合 C# 同步异步、多线程、线程池、Task 教学讲义整理的学习笔记。