🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の DoEvents — Excel の「応答なし」を止める(そしてマクロが二重に走ってしまう理由)

|

Excel VBA の DoEvents — Excel の「応答なし」を止める(そしてマクロが二重に走ってしまう理由)

TL;DRDoEvents はマクロを一瞬だけ止め、コードが忙しい間にキューへ溜まったクリック・ キー入力・再描画を Excel に処理させます。これがウィンドウを**「応答なし」へ灰色に落ちるのを 防ぎ、機能するキャンセルボタン**を可能にします。ただしその同じ譲りが、マクロの途中で制御を ユーザーへ返しもする — だからユーザーはあなたのボタンをもう一度押し、一つ目のマクロの中で 二つ目のコピーを起動できてしまいます。その再入を実行中フラグで守り、DoEvents は毎回ではなく 一定間隔で呼びましょう。

Private mRunning As Boolean

Sub LongJob()
    If mRunning Then Exit Sub         ' 再入ガード - 二つ目のコピーの起動を拒む
    mRunning = True
    On Error GoTo CleanExit
    Dim i As Long
    For i = 1 To 500000
        ' ... i 行目の処理 ...
        If i Mod 1000 = 0 Then DoEvents   ' ときどき譲って Excel を生かしておく
    Next i
CleanExit:
    mRunning = False                  ' エラーのあとでも必ずフラグを下ろす
End Sub

長い VBA ループは Excel のたった一本のスレッドの上で走り、その間 Excel は他に何もできません — 再描画 も、クリックの受付も、タイトルバーの更新もです。数秒後、Windows はアプリがハングしたと判断し、 マクロは完璧に動いているのに**「応答なし」**を横断させて焼き付けます。DoEvents は逃がし弁です。 本記事は 1 つの考えを軸にしています — DoEvents は制御を返すことで応答性を買う。そして「制御を 返す」には、ユーザーに物事を壊す制御を返すことも含まれる。 これは「Excel を滑らかにする」ただの 一行ではありません。取引であり、その再入という代金を支払わなければなりません。

この記事で学べること

  • 考え方の軸 — 一本のスレッド、メッセージキュー、そしてループがウィンドウを固める理由
  • DoEvents が実際にすること — キューを空にして、再開する
  • 本当の危険 — 再入、そしてそれを止める実行中フラグのガード
  • なぜ間引かなければならないか — 毎回の DoEvents は実行時間を支配しうる
  • キャンセルボタンをどう可能にするか、そしてそれが ステータスバー とどうつながるか
  • DoEventsマルチスレッドではない理由、そしていっそ入れないほうがよいとき

考え方の軸:一本のスレッドと、決して読まれないキュー

Excel はあなたの VBA を、グリッドの描画・マウスの処理・リボンの更新に使うのと同じ一本のスレッドで 走らせます。マクロが走っている間、そのスレッドは 100% あなたのものです。ユーザーのあらゆるクリックと キー入力は消えるのではなく、メッセージキューに着地して待ちます。けれど誰もそのキューを読んでいま せん。スレッドがあなたのループの中で忙しいからです。画面は古くなり、キューは詰まり、数秒後に Windows はウィンドウの上に「応答なし」を描きます。

DoEvents はそのキューを読みます。呼び出すと、VBA はマクロを一時停止し、Excel に待っているすべて を処理させ — 画面を再描画し、クリックを処理し、発火したイベントハンドラーを走らせ — そして DoEvents の次の行に制御を返し、マクロが続きます。

' ... ループがスレッドを独占し、ウィンドウが固まっている ...
DoEvents   ' Excel がキューを空にする: 再描画し、クリックを処理し、ハンドラーを走らせ、そして戻る
' ... マクロはここから再開する ...

これは本当に役立ちます。ウィンドウは応答し続け、設定したステータスバーは実際に描き直され、ユーザーは 操作できます。問題は、まさにその最後の部分です。

本当の危険:再入

DoEvents を単に遅いのではなく危険にする失敗がこれです。あなたのマクロはボタンから起動されます。 途中で DoEvents を呼びます。Excel はキューに溜まった入力を処理し — そしてマクロが「時間が かかっている」のを見たユーザーは、同じボタンをもう一度クリックしていたのです。そのクリックは いまあなたの DoEvents最中に処理されるので、Excel は一つ目がまだループの中で一時停止して いるうちに二つ目のマクロの実行を始めます。いま 2 つのコピーが同じデータの上で交錯します。

結果は間違いから壊滅までさまざまです。行が二重に処理される、カウンターが二重に数える、両方の実行が 同じファイルを書く、あるいは二つ目の実行が一つ目の想定していた安定した状態を書き換えて真っ向から エラーになる。これが再入であり、DoEvents のナンバーワンのバグ — 自分自身を発火させる イベントハンドラーと同じ失敗の系統です。

ガードは、二度目の侵入を拒むモジュールレベルのフラグです。

Private mRunning As Boolean

Sub LongJob()
    If mRunning Then Exit Sub    ' すでに実行中 - 余分なクリックは無視する
    mRunning = True
    On Error GoTo CleanExit
    ' ... DoEvents を含むループ ...
CleanExit:
    mRunning = False             ' 成功時にもエラー時にも下ろす。さもないと自分を締め出す
End Sub

ここでも CleanExit の復元は省略可能ではありません。エラーが mRunning = False を飛ばせば、フラグ は True のまま残り、プロジェクトをリセットするまでマクロは二度と走るのを拒みます。このクラスターの どのスイッチとも同じ On Error の心得です。

間引くこと:DoEvents はタダではない

再入がなくても、DoEvents にはコストがあります。メッセージキューを空にして OS へ譲るのには実時間が かかり — しばしばループ一回分の小さな処理よりずっと多くかかります。タイトなループの毎回呼べば、 2 秒のマクロを 30 秒にしてしまうことがあり、時間の大半を働くのではなく譲るのに費やします。

ですから一定間隔で呼びましょう — 1,000 行ごと、あるいはタイマーで 4 分の 1 秒ごと — 毎回ではなく。

For i = 1 To n
    ' ... 処理 ...
    If i Mod 1000 = 0 Then DoEvents   ' 毎行の代金を払わずに、十分応答的
Next i

間隔はダイヤルです。より頻繁ならウィンドウはきびきびし、キャンセルボタンはより応答的になります。より まれなら生の処理速度は上がります。i Mod 1000 は、速い行単位の処理には良い出発点です。各反復に どれだけかかるかに合わせて調整しましょう。

それが可能にするキャンセルボタン

再入が見せてくれた利点は、人が最も欲しがる機能でもあります。DoEvents が Excel に実行中のクリックを 処理させるおかげで、ユーザーはキャンセルボタンをクリックし、マクロがまだループしているうちに それを届かせることができます。ボタンにパブリックなフラグを配線し、各 DoEvents の後にそれを確認 します。

Public gCancel As Boolean          ' キャンセルボタンのクリックハンドラーが True にする

Sub LongJob()
    Dim i As Long
    For i = 1 To 500000
        ' ... 処理 ...
        If i Mod 1000 = 0 Then
            DoEvents
            If gCancel Then Exit For   ' クリックが届いた - きれいに止める
        End If
    Next i
End Sub

DoEvents がなければ、キャンセルのクリックはマクロが自力で終わるまでキューに座っているだけ — 役に 立ちません。あれば、ボタンは機能します。ステータスバー のメッセージと 組み合わせ、ユーザーが進捗を見られると同時に止められるようにしましょう。

マルチスレッドではない理由 — そして省くべきとき

DoEvents は、あなたのマクロをバックグラウンドや別のスレッドで走らせるのではありません。すべては 依然として一本のスレッドの上で直列に走り、DoEvents はあなたの処理のかたまりの間に Excel の保留中の 仕事を差し挟むだけです。ループは速くなりません — むしろ遅くなります — ただ UI をブロックするのを やめるだけです。本当に処理を並列に走らせる必要があるなら、それはまったく別の道具(別プロセス、あるいは スレッドを扱える言語)であって、DoEvents ではありません。

そして正直な既定:マクロが 1、2 秒未満で終わるなら「応答なし」は決して起きないので、DoEvents は 何の見返りもなくコストと再入のリスクを足すだけ — 入れないでください。手を伸ばすのは、固まった ウィンドウが本当の問題になるほど実行が長いときだけにし、そうしたら再入を守り、呼び出しを間引きます。 ループのあちこちに DoEvents を散りばめた速いマクロは、それのない同じマクロより遅く、そして脆く なります。

ExcelMaster の活用

DoEvents をうまく使うことは、判断の束です。長い実行のときだけ、毎回ではなく一定間隔で、再入ガードの 後ろで、フラグをエラーハンドラーの中で下ろし、そしてキャンセル確認と進捗メッセージと組み合わせる。 どれか一つでも外せば、二重に走るマクロ、這うように遅いマクロ、あるいは決して発火しないキャンセル ボタンが手に入ります。

ExcelMaster は、その判断を あなたの代わりに下します。「応答的なままキャンセルできる長いインポート」を頼めば、再入を止める 実行中フラグを足し、ほどよい間隔で DoEvents を呼び、その直後にキャンセルフラグを確認し、すべてを CleanExit ハンドラーの中で下ろします — だから、固まったウィンドウや二重実行ではなく、応答的で 中断できるマクロが手に入ります。

よくある質問

Excel VBA の DoEvents は何をしますか?

DoEvents はマクロを短く一時停止し、コードが走っている間にキューへ溜まった入力と再描画の仕事を Excel に処理させます — クリック、キー入力、画面の再描画、そして発火したイベントハンドラーです。 それから次の行に制御を返し、マクロが続きます。これが、長いマクロが Excel ウィンドウを「応答なし」へ 固めるのを防ぐものです。

マクロの実行中に Excel が「応答なし」と出るのはなぜですか?

あなたのマクロが Excel の唯一のスレッドを使っているため、マクロが譲るまで Excel は再描画も入力の処理 もできないからです。数秒後、Windows はアプリをハングとしてマークします — マクロがちゃんと動いていても です。ループの中で一定間隔で DoEvents を足せば Excel は息をつけ、「応答なし」の表示は消えます。

DoEvents は危険ですか?

再入を通じて、危険になりえます。DoEvents が Excel に実行中のクリックを処理させるため、一つ目の実行 が一時停止している間にユーザーが(ボタンをもう一度クリックして)同じマクロを再起動でき、2 つのコピー が同時に走って互いの仕事を壊します。モジュールレベルの「実行中」フラグでガードし、すでに実行中なら マクロを抜けさせ、そのフラグをエラーハンドラーの中で下ろしましょう。

ループの毎回で DoEvents を呼ぶべきですか?

いいえ — 間引きましょう。DoEvents には実コストがあり、タイトなループの毎回で呼ぶと実行時間を支配し、 マクロをはるかに遅くしかねません。代わりに一定間隔で呼び(たとえば If i Mod 1000 = 0 Then DoEvents)、 各反復にどれだけかかるかに合わせて間隔を調整しましょう。

DoEvents でマクロはマルチスレッドや高速になりますか?

いいえ。すべては依然として一本のスレッドの上で直列に走り、DoEvents はあなたのコードのかたまりの間に Excel の保留中の UI の仕事を差し挟むだけです。ループは速くなりません — たいていわずかに遅くなります — ただインターフェイスをブロックするのをやめるだけです。本物の並列処理には、DoEvents ではなく別の プロセスが必要です。

検証環境

検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-18。

関連ガイド: VBA StatusBar · VBA DisplayAlerts · VBA ScreenUpdating · VBA EnableEvents · VBA On Error