关于本文
本文查阅了Microsoft Learn的VBA DoEvents官方文档,并深入到Excel VBA事件处理与重入的视角进行了整理与总结。验证状态:📘 已确认官方规范・Excel实机未验证
DoEvents并非是将长时间运行的处理转变为真正异步处理的功能。
在Excel VBA中执行较长的循环时,界面可能会出现看起来卡死的情况。调用DoEvents可以使程序更容易响应操作,这是因为VBA暂时将控制权交给了操作系统,并创造了处理等待中事件的机会。
这里重要的一点是,不要将DoEvents视为“实现异步化的指令”。
没有DoEvents会发生什么
sequenceDiagram
participant U as User
participant E as Excel UI
participant V as VBA
U->>E: Click / repaint等のイベント
V->>V: 長いloopを継続
Note over E,V: VBAが処理を占有
E-->>U: 反応しにくく見える
当VBA持续进行长时间的处理,而UI端处理事件的机会减少时,重新绘制(重绘)和对输入的响应就会变慢。
调用DoEvents后控制权会返回
在Microsoft Learn中,DoEvents被解释为让操作系统能够处理其他event的execution yield函数。
sequenceDiagram
participant V as VBA
participant O as Excel / OS event processing
participant U as User
V->>V: loopを実行
V->>O: DoEvents
U->>O: input / repaint
O->>O: queue中のeventを処理
O-->>V: controlを戻す
V->>V: loopの続きを実行
这并不意味着DoEvents前后处理主体会移动到不同的thread中。
最小示例
Sub DemoDoEvents()
Const MaxCount As Long = 5000000
Dim i As Long
On Error GoTo CleanUp
For i = 1 To MaxCount
If i Mod 10000 = 0 Then
Application.StatusBar = _
"Processing: " & Format(i / MaxCount, "0%")
DoEvents
End If
Next i
CleanUp:
Application.StatusBar = False
End Sub
这里并非在每个loop中都调用DoEvents,而是以一定的间隔向UI返回控制权。
虽然方便,但存在“重入”的问题
在DoEvents执行期间,可能会执行其他的event procedure。
例如,从Button_Click开始执行一项长处理,如果在途中的DoEvents期间再次点击了相同的Button,则相同的处理可能会重叠开始。
flowchart TB
A[Button_Click] --> B[長い処理]
B --> C[DoEvents]
C --> D{同じbuttonが再度押された?}
D -->|No| E[元の処理へ戻る]
D -->|Yes| F[同じevent procedureへ再入]
F --> G[状態の競合・二重実行]
Microsoft Learn也提醒开发者注意,在通过DoEvents交出control的期间,应避免相同的procedure被重新执行。
简单的防止重入
对于现有宏的小规模对策,可以使用运行中flag(标志)。
Private mRunning As Boolean
Sub LongTask()
Const MaxCount As Long = 5000000
Dim i As Long
If mRunning Then
MsgBox "已经在运行中。"
Exit Sub
End If
mRunning = True
On Error GoTo CleanUp
For i = 1 To MaxCount
If i Mod 10000 = 0 Then
Application.StatusBar = _
"Processing: " & Format(i / MaxCount, "0%")
DoEvents
End If
Next i
CleanUp:
Application.StatusBar = False
mRunning = False
End Sub
这并非thread-safe(线程安全)的并发处理控制。它只是为了让Excel VBA更难重复启动相同操作的简单状态管理。
区分DoEvents与异步处理
flowchart LR
A[DoEvents] --> B[イベント処理へ一時的にcontrolを渡す]
C[非同期I/O] --> D[待ち時間をcallback/event等で扱う]
E[別process/thread] --> F[処理主体そのものを分ける]
B -. 同じではない .- D
B -. 同じではない .- F
对于HTTP通信等外部I/O等待时间较长的处理,采用目标API所提供的async mode或使用event/callback进行设计的思路往往更为本质。
DoEvents不是“将同步处理转换为async的魔法”。
与Windows的message loop的关系在多大程度上是一样的?
应避免将VBA/Excel的内部实现简单地与Win32典型的GetMessage/DispatchMessage loop画等号。
另一方面,作为学习Windows GUI一边处理message/event一边运行这一概念的入口,DoEvents是一个有趣的题材。
flowchart TB
A[Windows GUIのevent/message] --> B[Excel application]
B --> C[VBA event procedure]
C --> D[長い同期処理]
D --> E[DoEvents]
E --> B
不要断言“VBA的DoEvents内部代码就是这张图本身”,而是将其作为理解事件驱动的概念图来对待。
何时使用
DoEvents在小型现有宏中想要实现以下功能时往往非常有用:
想要更新StatusBar等进度
想要接受取消操作
不希望UI显得完全没有响应
另一方面,对于长时间、复杂的处理,状态共享较多的处理,以及以外部I/O等待为核心的处理,应考虑其他设计方案。
小结
DoEvents会暂时将control交给OS来处理event
它本身并非异步处理
在某些场景下可以改善UI的响应
注意DoEvents期间的重入问题
面对较长的I/O等待,请考虑async API或其他设计
可用作学习Windows event/message处理的入口,但切勿将会内部实现过度简化


コメント