ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

8_20笔记整理

8_20笔记整理 C# 多线程基础笔记一、为什么需要多线程一个进程至少包含一个线程主线程UI线程。主线程负责界面响应窗体显示、按钮点击等。若在主线程执行耗时任务如循环、大量计算界面会卡死。解决方案将耗时任务放到分线程执行。一个进程 │ ├── 主线程UI响应 │ ├── 分线程1耗时任务 ├── 分线程2耗时任务 └── 分线程3耗时任务核心思想不要让耗时任务长期占用UI线程。二、Thread —— 手动创建线程1. 传统写法三步ThreadStart threadStart new ThreadStart(TestMethod); Thread t1 new Thread(threadStart); t1.Start();ThreadStart委托指向要执行的方法。new Thread(...)创建线程对象尚未启动。Start()启动线程真正执行方法。2. 简写形式编译器自动处理委托Thread t1 new Thread(TestMethod); t1.Start();3. 使用 Lambda 表达式Thread t2 new Thread(() { Console.WriteLine(匿名委托直接开启分线程); }); t2.Start();Lambda 表达式相当于临时定义的方法。三、ThreadPool —— 线程池1. 为什么需要线程池频繁new Thread()创建和销毁线程成本高。线程池由 .NET 管理复用线程减少开销。任务 → 线程池分配空闲线程 → 执行 → 线程回归池中复用2. 向线池提交任务ThreadPool.QueueUserWorkItem((s) { Console.WriteLine(${s}); }, 你好);第一个参数要执行的方法可带参数。第二个参数传递给方法的参数。注意线程池不保证任务执行顺序四ManualResetEvent核心概念ManualResetEvent resetEvent new ManualResetEvent(false);false→红灯阻塞等待true→绿灯放行eg:using System; using System.Threading; ​ class Program { static void Main() { ManualResetEvent mre new ManualResetEvent(false); ​ Thread t new Thread(() { Console.WriteLine(子线程正在等待信号...); mre.WaitOne(); //卡住等待Set() Console.WriteLine(子线程收到信号开始执行); }); ​ t.Start(); ​ Thread.Sleep(3000); Console.WriteLine(主线程发送信号Set); mre.Set(); //放行 ​ Console.ReadLine(); } }运行子线程卡住 3 秒主线程 Set 之后子线程继续。关系图整体结构多线程 │ ┌───────────┴───────────┐ ↓ ↓ Thread ThreadPool │ │ ↓ ↓ new Thread() QueueUserWorkItem() │ ↓ Start() │ ├── Sleep() ├── Join() └── ThreadState ​ 线程同步 │ ↓ ManualResetEvent │ │ ↓ ↓ WaitOne() Set() 等待 发信号同步、异步与 Task 笔记一、同步 vs 异步核心概念1. 同步核心定义前面的任务没完成后面的代码不能继续执行。MessageBox.Show(开始); for (int i 0; i 10; i) { Console.WriteLine(66666); } MessageBox.Show(结束);执行顺序开始 → for循环 → 结束必须一个一个执行。问题如果中间有耗时操作如大量循环主线程会被一直占用导致 WinForms 界面卡死。2. 异步重要认知异步 ≠ 多线程更准确的定义当前任务未完成时不一定一直占用当前线程等待允许程序在等待期间继续处理其他事情示意图同步 任务A → 等待A完成 → 任务B 异步 任务A ──────────────→ 完成 ↓ 当前线程先去干别的事情 → 任务B同步/异步 与 单线程/多线程 是两个不同维度的概念不要混淆。单线程\多线程 与同步\异步单线程、多线程描述「干活的人线程有几个」。同步、异步描述「调用者要不要原地等待结果」。3.举一个生活全景例子场景烧水单线程‑同步你自己守水壶旁边啥也不干一直等水烧开。单线程‑异步按下烧水开关你去扫地水开蜂鸣器叫你。没有第二个人还是你一个人。多线程‑同步叫另一个人专门看水壶你站边上不走等他告诉你水开了你再走。多线程‑异步叫另一个人看水壶你直接去看电视他水开了过来通知你。二、Thread 解决什么问题Thread解决的是把任务放到另一个线程执行。Lambda 方式开线程Thread t3 new Thread(() { int res Fib(5); MessageBox.Show(res.ToString()); }); t3.Start();Lambda 表达式本质是一个匿名方法/委托。三、WinForms 跨线程访问 UI1. 为什么不能直接修改 Label// ❌ 错误写法 Thread t4 new Thread(() { int result Fib(10); label1.Text result.ToString(); // 报错 });原因WinForms UI 控件由主线程UI线程创建和维护分线程不能直接访问 UI 控件会抛出跨线程操作无效从不是创建控件的线程访问它2. Invoke —— 切换回 UI 线程// ✅ 正确写法 Thread t4 new Thread(() { int result Fib(10); Invoke(new Action(() { label1.Text result.ToString(); // 交回 UI 线程执行 })); }); t4.Start();流程分线程 │ ├── Fib(10) 计算 │ └── 得到 result ↓ Invoke ↓ UI 线程 ↓ label1.Text result四、Task 基础1. Task 是什么类型说明Task表示一个没有返回值的异步操作Task表示一个有返回值的异步操作返回值类型为 T纠正Task 不是Action 与 Func 的结合而是异步操作的抽象。2. Task 和 Thread 的关系Task 任务/异步操作的抽象 Thread 操作系统线程Task不直接等于ThreadTask.Run()通常使用线程池线程执行3.创建 Task 的方式1. 使用构造函数不推荐Taskint t new Taskint(new Funcint(() { return Fib(10); })); t.Start(); // 需要手动 Start int result t.Result;2. 使用 Task.Run()推荐Taskint t Task.Run(() { return Fib(10); });Task.Run() 把任务提交给线程池执行并返回 Task 对象。线程池有容量可以直接用3. 无返回值示例Task task Task.Run(() { Thread.Sleep(1000); Console.WriteLine(点外卖); });五、等待多个 Task1. WaitAny —— 任意一个完成int ind Task.WaitAny(tasks); // 大约 1 秒返回ind 最先完成的任务索引0阻塞当前线程等待任意一个任务完成返回值最先完成的 Task 在数组中的索引2. WhenAny —— 任意一个完成非阻塞Task.WhenAny(tasks).ContinueWith(t { Console.WriteLine(任意一个任务完成进入 ContinueWith); });WhenAny返回 Task不阻塞可以用ContinueWith或await跟进。3. WhenAll —— 全部完成Task.WhenAll(tasks).ContinueWith(t { Console.WriteLine(所有任务都完成了吃饱了); });对比方法行为阻塞/非阻塞WaitAny任意一个完成即返回阻塞WhenAny任意一个完成即继续非阻塞WhenAll全部完成才继续非阻塞六、知识体系全览同步顺序执行耗时卡UI ↓ 异步不必一直等待 ↓ Task / TaskT │ ├── Task无返回值 │ └── TaskT有返回值 ↓ Result获取结果可能阻塞 ↓ Task.Run() → 线程池执行 ↓ ContinueWith任务延续 ↓ 多 Task 并发 │ ├── WaitAny阻塞任意完成 ├── WhenAny非阻塞任意完成 └── WhenAll非阻塞全部完成 ↓ WinForms UI 更新 ↓ Invoke切回 UI 线程 ↓ 后续学习 async/await窗体数据传递一、概述在 WinForms 开发中父窗口与子窗口之间经常需要传递数据主要方向有两种方向说明父 → 子父窗口把数据传给子窗口如编辑时传入 Student 对象子 → 父子窗口把数据传回父窗口如新增或编辑后返回结果二、方式一构造函数传值父 → 子核心思想父窗口通过子窗口的构造函数把数据传进去。使用场景编辑数据传入已有 Student子窗口初始化时就需要数据父窗口 → 子窗口代码示例父窗口MainFormStudent student GetSelectedStudent(); ​ using (var af new StuAddOrEdit(student)) { af.ShowDialog(); // 以模态对话框打开 }子窗口StuAddOrEditprivate Student _currentStudent; ​ public StuAddOrEdit(Student student) { InitializeComponent(); _currentStudent student; // 初始化时显示数据 txtName.Text student.Name; txtAge.Value student.Age; }数据流向MainForm │ │ Student ↓ StuAddOrEdit三、方式二公共属性 DialogResult子 → 父核心思想子窗口定义一个公共属性如ResultStudent用于存放要返回的数据父窗口通过ShowDialog()判断用户是否点击确定如果确定则读取该属性获取数据使用场景新增窗口编辑窗口设置窗口登录窗口用户填写完表单后把结果交给父窗口代码示例子窗口StuAddOrEdit// 公共属性用于返回数据 public Student ResultStudent { get; private set; } ​ private void btnEnsure_Click(object sender, EventArgs e) { Student stu new Student() { Name txtName.Text, Age (int)txtAge.Value }; ResultStudent stu; // 保存到属性 DialogResult DialogResult.OK; // 告诉父窗口确定提交 Close(); }父窗口MainFormusing (var af new StuAddOrEdit()) { if (af.ShowDialog() DialogResult.OK) { Student stu af.ResultStudent; // 从属性获取数据 // 处理 stu添加到列表、保存到数据库等 } }关键点DialogResult → 告诉父窗口子窗口是否确定提交 ResultStudent → 真正把数据带回来ShowDialog()返回的是DialogResult不是 Student数据流向StuAddOrEdit │ │ ResultStudent ↓ MainForm四、方式三委托 / 事件子 → 父主动通知核心思想子窗口定义事件父窗口订阅该事件子窗口在用户点击确定时触发事件主动把数据推送给父窗口使用场景子窗口主动通知父窗口某件事情发生了例如StudentAdded、StudentUpdated、DataChanged、LoginSuccess代码示例子窗口StuAddOrEdit// 1. 定义事件 public event ActionStudent OnSendStudent; ​ private void btnEnsure_Click(object sender, EventArgs e) { Student stu new Student() { Name txtName.Text, Age (int)txtAge.Value }; // 2. 触发事件通知订阅者 OnSendStudent?.Invoke(stu); Close(); }父窗口MainForm// 3. 订阅事件 using (var af new StuAddOrEdit()) { af.OnSendStudent Af_OnSendStudent; af.ShowDialog(); } ​ // 4. 事件处理方法 private void Af_OnSendStudent(Student stu) { _stuDal.Addstu(stu); BindDataGridView(StudentDAL.stus); }属性返回 vs 事件 —— 区别在哪方式数据获取方式特点属性返回父窗口主动去拿Student stu af.ResultStudent;事件子窗口主动通知OnSendStudent?.Invoke(stu);数据流向StuAddOrEdit │ │ 触发事件 ↓ OnSendStudent │ ↓ MainForm三种方式对比方式数据方向推荐程度典型场景构造函数父 → 子⭐⭐⭐⭐⭐编辑时传 Student属性 DialogResult子 → 父⭐⭐⭐⭐⭐表单提交结果委托 / 事件子 → 父⭐⭐⭐⭐⭐子窗口主动通知
返回列表