09-C#

发布时间:2026/5/20 10:56:20

09-C# C#.Net-多线程-基础篇-学习笔记一、核心概念理解1.1 进程与线程进程(Process)计算机中的虚拟记录描述应用程序运行时消耗的各种资源集合包含CPU 内存 磁盘IO 网络资源类比一个公司的整体运作线程(Thread)程序进程中执行具体动作的最小执行流从点击按钮到网络通信都是线程在执行类比公司中的具体员工特点线程依附于进程存在进程消失则线程也消失句柄(Handle)long类型的数字对应程序中的某个小部件是操作程序的最小单位标识1.2 多线程的原理CPU与多线程例如6核12线程的CPU通过逻辑切分将每个核心的执行时间分片每毫秒可执行的动作被切分成10000份执行特点宏观多个动作在一段时间内看似同时执行完毕(并发)微观同一时刻只能处理一件事(串行)操作系统不断调度动作A开始用某一分片结束时可能用另一分片二、同步与异步2.1 同步方法(Sync)特点private void DoSomethingSync() { // 线性执行从上往下依次执行 Console.WriteLine(步骤1); Thread.Sleep(1000); Console.WriteLine(步骤2); Thread.Sleep(1000); Console.WriteLine(步骤3); }优点符合人类思维逻辑代码易读有序执行结果可预测消耗计算机资源少缺点执行慢会卡顿界面只有一个线程(主线程/UI线程)参与计算CPU忙碌时无暇处理其他任务生活类比真心请人吃饭Richard请Vn吃饭Vn说还要忙一会儿Richard说那我等你忙完一起去2.2 异步方法(Async)特点private void DoSomethingAsync() { // 开启新线程执行 Task.Run(() { Console.WriteLine(步骤1); Thread.Sleep(1000); }); Task.Run(() { Console.WriteLine(步骤2); Thread.Sleep(1000); }); // 主线程不等待继续执行 Console.WriteLine(主线程继续); }优点执行快不卡顿界面多个线程并发执行提高程序响应能力缺点无序执行结果不可预测消耗更多计算机资源编程复杂度增加生活类比客气请人吃饭Richard客气地请Vn吃饭Vn说还要忙一会儿Richard说那你先忙我先去吃了核心理念以资源换时间使用大量资源降低时间成本三、C#中的多线程实现3.1 Thread(最原始)Thread thread new Thread(() { Console.WriteLine(Thread执行); }); thread.Start(); thread.IsBackground true; // 后台线程进程结束线程随之消失 thread.IsBackground false; // 前台线程进程结束线程执行完才消失3.2 ThreadPool(线程池)管理一组可重用的线程避免频繁创建和销毁线程的开销// 将工作项加入线程池队列 ThreadPool.QueueUserWorkItem(state { Console.WriteLine($ThreadPool执行线程ID: {Thread.CurrentThread.ManagedThreadId}); }); // 带参数版本 ThreadPool.QueueUserWorkItem(state { string msg (string)state; Console.WriteLine($参数: {msg}); }, hello);⚠️ ThreadPool 无法精确控制线程也不支持返回值和取消实际开发中优先使用 Task。3.3 Task(推荐使用)Task的优势.NET Framework 3.0时代实现C#中多线程操作的最佳实现丰富的API满足各种开发场景Task操作的线程来自线程池Task开启方式// 方式1new Task Start Task task new Task(() { Console.WriteLine(Task执行); }); task.Start(); // 方式2Task.Run(推荐) Task.Run(() { Console.WriteLine(Task执行); }); // 方式3TaskFactory TaskFactory factory new TaskFactory(); factory.StartNew(() { Console.WriteLine(Task执行); }); // 方式4带返回值 Taskint task Task.Run(() { return DateTime.Now.Year; });线程标识// 通过线程ID确定是否为同一线程 int threadId Thread.CurrentThread.ManagedThreadId; Console.WriteLine($当前线程ID: {threadId:00});四、Task的核心方法4.1 等待方法Wait() - 单个任务等待Task task Task.Run(() { Thread.Sleep(3000); Console.WriteLine(任务完成); }); task.Wait(); // 主线程等待会卡顿界面 task.Wait(1000); // 限时等待过时不候 task.Wait(TimeSpan.FromMilliseconds(1100)); // 使用TimeSpanWaitAll() - 等待所有任务ListTask tasks new ListTask(); tasks.Add(Task.Run(() DoWork1())); tasks.Add(Task.Run(() DoWork2())); tasks.Add(Task.Run(() DoWork3())); Task.WaitAll(tasks.ToArray()); // 等待所有任务完成WaitAny() - 等待任意一个任务Task.WaitAny(tasks.ToArray()); // 任意一个任务完成即返回4.2 回调方法(不阻塞)ContinueWith() - 单个任务回调Task task Task.Run(() { Thread.Sleep(3000); return 结果; }); task.ContinueWith(t { Console.WriteLine($任务完成结果{t.Result}); });ContinueWhenAny() - 任意一个完成时回调TaskFactory factory new TaskFactory(); factory.ContinueWhenAny(tasks.ToArray(), completedTask { Console.WriteLine(有一个任务完成了); // 不卡顿界面用户体验好 });ContinueWhenAll() - 所有完成时回调factory.ContinueWhenAll(tasks.ToArray(), completedTasks { Console.WriteLine(所有任务都完成了); });4.3 Delay() - 延迟执行// Thread.Sleep - 阻塞主线程 Thread.Sleep(3000); // 主线程卡顿3秒 // Task.Delay - 不阻塞配合ContinueWith使用 Task.Delay(3000).ContinueWith(t { Console.WriteLine(3秒后执行); // 可能是新线程也可能是主线程执行 });五、应用场景5.1 适合使用多线程的场景场景1多个独立任务并发执行// 同时查询接口、缓存、数据库 ListTask tasks new ListTask(); tasks.Add(Task.Run(() QueryFromApi())); tasks.Add(Task.Run(() QueryFromCache())); tasks.Add(Task.Run(() QueryFromDatabase())); Task.WaitAll(tasks.ToArray());场景2项目实战 - 多人协作开发// 每个开发人员对应一个线程 TaskFactory factory new TaskFactory(); ListTask devTasks new ListTask(); devTasks.Add(factory.StartNew(() Coding(张三, 权限模块))); devTasks.Add(factory.StartNew(() Coding(李四, 支付模块))); devTasks.Add(factory.StartNew(() Coding(王五, 订单模块))); // 任意一人完成准备环境 factory.ContinueWhenAny(devTasks.ToArray(), t { Console.WriteLine(有人完成了开始准备环境); }); // 所有人完成庆祝 factory.ContinueWhenAll(devTasks.ToArray(), t { Console.WriteLine(所有人完成一起吃饭庆祝); });5.2 实际业务场景场景1数据查询优先级同时查询缓存、数据库、第三方接口使用WaitAny哪个先返回数据就用哪个其他线程自动放弃场景2首页数据加载首页包含考勤信息、年度Top10、季度Top10、公告、通知每个模块开启一个线程并发查询使用WaitAll等待所有数据组装后返回前端提高查询性能5.3 不适合使用多线程的场景单个数据库查询(只有一个动作)简单的顺序操作需要严格顺序的业务逻辑六、父子线程Task parentTask new Task(() { Console.WriteLine(父线程开始); Task childTask1 new Task(() { Thread.Sleep(1000); Console.WriteLine(子线程1); }); Task childTask2 new Task(() { Thread.Sleep(1000); Console.WriteLine(子线程2); }); childTask1.Start(); childTask2.Start(); // 如果不等待子线程父线程可能先结束 // childTask1.Wait(); // childTask2.Wait(); Console.WriteLine(父线程结束); }); parentTask.Start(); parentTask.Wait(); // 主线程等待父线程注意事项父线程等待时子线程可能还未执行完需要在父线程中显式等待子线程否则父线程结束子线程内容可能未执行完七、实用技巧7.1 线程ID格式化// 格式化为两位数方便查看 string threadId Thread.CurrentThread.ManagedThreadId.ToString(00); Console.WriteLine($线程ID: {threadId});7.2 时间戳格式化string timestamp DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss.fff); Console.WriteLine($时间: {timestamp});7.3 性能测试Stopwatch stopwatch new Stopwatch(); stopwatch.Start(); // 执行任务 DoSomething(); stopwatch.Stop(); Console.WriteLine($耗时: {stopwatch.ElapsedMilliseconds}ms);八、小结同步方法有序、慢、阻塞、资源消耗少异步方法无序、快、不阻塞、资源消耗多Task 是首选功能强大、API 丰富、来自线程池Wait 系列阻塞主线程会卡顿界面Continue 系列不阻塞用户体验好合理使用多线程并发场景才使用不要滥用

相关新闻