this.Invoke学习笔记
this.Invoke 学习笔记
接上一篇《WinForm 串口助手实战笔记》。上一篇里接收数据那段代码,套了一层 this.Invoke,我当时说”照着抄就行”。这一篇专门把这件事讲透,让你不仅会用,还能明白它到底在干嘛。
一、先认识线程
程序运行起来后,其实是一根”线”在从上往下一行行执行代码。这根”线”,就叫线程。
一个程序里可以同时有好几根这样的线在跑,各干各的。我们这个串口助手里,主要有两根:
| 线程 | 谁在跑 | 负责什么 |
|---|---|---|
| 界面线程 | 程序一启动就在 | 专门管窗口、按钮、文本框这些界面 |
| 后台线程 | 串口收到数据时临时冒出来 | 专门盯着串口,数据一来就接住 |
你可以把界面线程理解成”专门管柜台的人”,把后台线程理解成”专门在仓库搬货的人”。
二、为什么串口收到数据,不能直接改界面
这里有一条 C# 的硬规矩,一定要记住:
界面上的控件,只有界面线程能碰。
串口收到数据,是”后台线程”接住的。它手里拿着数据,想去改文本框,但文本框是”界面线程”管的,它碰不了。后台线程硬要去碰,程序就直接报错、闪退。
所以问题就来了:数据在后台线程手里,控件却只能界面线程碰,这个矛盾怎么解?
三、this.Invoke 是怎么帮忙的
解决办法很简单,一句话:后台线程自己不碰,把”改控件”这件事转交给界面线程去做。
this.Invoke 就是干这个的。它的意思可以直白地读成:
请你(界面线程)帮我做下面这件事。
代码长这样:
1 | private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) |
中间大括号里的 txtReceive.AppendText(msg) 才是真正”改文本框”的那一句。外面包的这一层 this.Invoke,就是”把这个活交给界面线程”。



四、把代码拆开,一个词一个词看
我们把 this.Invoke(new Action(() => { … })) 拆成四小块,每一块是干嘛的:
this
就是”当前这个窗口”,也就是 Form1。因为 Invoke 这个方法,是窗口自带的,所以用 this 来调用。
Invoke
意思是”委托界面线程执行,并且等它执行完再继续”。后面第二节会细讲这个”等”字。
new Action(…)
Action 可以理解成”一个任务包裹”。括号里装的就是要执行的代码。new Action 就是”打一个包,把里面的代码装进去”。
() => { … }
这个箭头符号读作”就这样做”。箭头左边是参数(这里没有参数,所以是空括号),箭头右边是要执行的代码。整句可以读成:”没有参数,直接执行大括号里这段代码”。
连起来整句话的意思就是:
把”往文本框里追加 msg 这段代码”打成一个包,交给界面线程去执行,并且等它执行完。
五、同步还是异步:Invoke 和 BeginInvoke
上面说到,Invoke 会”等执行完再继续”。这个”等”,就是同步的意思。
其实还有一个兄弟,叫 BeginInvoke,它”不等”。这就是同步和异步的区别。
| Invoke | BeginInvoke | |
|---|---|---|
| 交完任务后 | 原地等,界面更新完才继续 | 不停留,马上继续跑 |
| 叫法 | 同步 | 异步 |
| 好处 | 顺序稳稳的,简单好理解 | 后台线程不会被界面拖住 |
| 代价 | 界面慢的话,后台线程会被拖住 | 大量数据时,顺序可能乱 |
用大白话比喻:
- Invoke(同步):你去办事窗口交材料,然后站在原地等,工作人员办完了你才走。稳,但工作人员磨蹭你就得干等。
- BeginInvoke(异步):你把材料往窗口一递,扭头就走,不等人办完。快,但材料多了,办理顺序可能跟你递交的顺序不一样。
代码对比:
1 | // 同步:等界面更新完,才继续 |
用法几乎一模一样,就差一个 Begin 前缀。
六、到底该用哪个
记住一个简单的判断标准:
- 数据量不大、偶尔来几条:用 Invoke 就行。简单、稳、不容易出错,新手首选。
- 数据量非常大、更新非常频繁(比如一秒钟几百上千条):考虑用 BeginInvoke。因为 Invoke 每次都要等界面更新完,界面如果更新得慢,接收线程就会被卡住,数据越积越多。
用 BeginInvoke 的好处,就是避免接收线程被界面更新阻塞,让收数据这件事能一直顺畅地跑下去。
不过 BeginInvoke 不是免费的:它不保证顺序,数据来得太猛时,界面可能来不及消化,甚至出现堆积。所以简单串口助手用 Invoke 完全没问题,等你以后真遇到海量数据的场景,再切换 BeginInvoke,同时配合分批处理等手段。
一句话总结:入门阶段认准 Invoke,数据量巨大时才考虑 BeginInvoke。
七、进阶:更严谨的写法(可以先跳过)
实际项目里,还有一种更严谨的写法,会先判断一下”我现在是不是界面线程”。如果不是,才去委托;如果本来就是界面线程,就直接改。
1 | private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) |
这里新出现一个 InvokeRequired,它的意思是”需不需要委托”。是,就委托;不是,就直接改。
这段你眼下先看个眼熟就行,能看懂最好,看不懂完全不耽误。等线程这块你彻底吃透了,再回头来看它,会觉得特别自然。
八、小结
这篇你只需要记住三句话:
- 界面控件只有界面线程能碰,串口数据是后台线程收到的,所以不能直接改。
- this.Invoke 的作用,就是把”改控件”这件事交给界面线程去做,并且等它做完(同步)。
- 数据量巨大、更新频繁时,换 BeginInvoke,让接收线程不被界面更新拖住(异步)。
有了这个底子,以后你在任何”后台收到东西、要显示到界面”的地方,都知道该怎么写了。
