VBAのDoEventsは何をしている? ― 画面が固まる理由とイベント処理を理解する

VBA・Officeカテゴリを表すパンダのイラスト VBA・Office

この記事について
この記事はMicrosoft LearnのVBA DoEvents公式資料を確認し、Excel VBAのイベント処理と再入の観点まで掘り下げて整理しています。

検証ステータス:📘 公式仕様確認済み・Excel実機未確認
DoEventsは長時間処理を本当の非同期処理へ変える機能ではありません。

Excel VBAで長いループを実行すると画面が固まったように見えることがあります。DoEventsを呼ぶと操作を受け付けやすくなるのは、VBAが一時的に制御をOSへ渡し、待っているイベントを処理する機会を作るためです。

ここで重要なのは、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はOSが他の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はOSへ一時的にcontrolを渡してeventを処理させる

  • 非同期処理そのものではない

  • UIの反応を改善できる場面はある

  • DoEvents中の再入に注意する

  • 長いI/O待ちはasync APIや別設計を検討する

  • Windowsのevent/message処理を学ぶ入口として使えるが、内部実装を単純化しすぎない

公式情報・一次情報

GitHubサンプル

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました