VBAのOnTimeとDoEventsを再入として理解する

VBAのOnTimeとDoEventsを再入として理解する

ExcelのメインUIスレッドを明け渡す処理と、予約マクロの実行タイミングを整理する

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

Excel VBAで長い処理を実行していると、UserFormの表示更新、画面描画、シートイベント、Application.OnTime で予約した処理などが、期待したタイミングですぐには動かないことがあります。これは、多くのVBA処理がExcelのメインUIスレッド上で動き、その処理が続いている間はExcel側のメッセージ処理に制御が戻りにくいからです。

この記事では、DoEvents を「待機」ではなく「Excel側へ一時的に制御を戻す命令」として整理します。そのうえで、OnTime は別スレッドで割り込む仕組みではなく、Excelが実行できる状態になったときに同じ実行系で呼ばれる予約実行だと位置づけます。

実務上は、DoEvents を回した回数や Application.Ready だけを根拠にせず、自分が予約した OnTime プロシージャが実際に呼ばれたことを、自前のフラグで確認するのが自然です。ただし、この方法はExcel内部のイベントキューが完全に空になったことを証明するものではありません。確認できるのは、少なくとも予約した OnTime が実行されたという事実です。

本記事の対象とゴール

想定読者

  • Excel VBAで長時間処理、UserForm、画面更新、イベント処理を扱っている人

  • DoEvents を入れると何が起きるのかを、感覚ではなく実行構造で理解したい人

  • Application.OnTime を使った待ち合わせや遅延実行で、意図しない再入や無限待ちを避けたい人

  • Application.Ready、DoEvents、自前フラグの役割を切り分けたい人

本記事で得られること

  1. DoEvents がExcelの処理を完全に終わらせる命令ではないことを説明できる。

  2. OnTime が別スレッド実行ではなく、Excelが実行可能なタイミングで呼ばれる予約実行だと判断できる。

  3. OnTime の実行確認には、自前フラグ、タイムアウト、再入防止が必要だと設計できる。

本記事で扱わないこと

  • WindowsメッセージループやCOM STAの厳密な内部仕様

  • Excel全バージョンにおける Application.OnTime の細かな差分

  • OnTime を使った本格的なスケジューラ実装

  • マルチスレッド処理や非同期処理の一般論

先に結論

DoEvents は、VBAが占有しているExcelのメインUIスレッドを一時的に明け渡し、Excel側のメッセージ処理を進めるための命令です。ただし、Excel内部の全処理完了を保証する命令ではありません。

Application.OnTime は、指定時刻になったら別スレッドで横から割り込む仕組みではありません。Excelがそのプロシージャを実行できる状態になったとき、同じExcelの実行系の上で呼ばれます。

そのため、OnTime の実行を待つなら、Application.Ready だけを見るより、自分で用意したフラグを見る方が目的に合います。ただし、フラグ待ちは必ずタイムアウト付きにし、DoEvents 中に別の処理が入り込む再入リスクも前提にします。

第1章 VBA実行中にExcel側の処理が止まる理由

この章では、最初に DoEvents と OnTime が問題になる背景を固定します。論点は、Excelが止まっているのではなく、VBA処理がExcelの実行系を占有していることです。

1.1 ExcelのメインUIスレッドをVBAが使っている

Excel VBAで長い処理を実行していると、UserFormの表示更新、シートの再描画、ユーザー操作への反応、シートイベント、ブックイベント、タイマー系の処理が遅れることがあります。

これは、Excel VBAの処理が基本的にExcelのメインUIスレッド上で実行されるためです。長いVBA処理が走っている間、Excelはその処理を継続することを優先します。その結果、通常であればExcelが処理するはずの画面描画やイベント処理が、すぐには進みません。

この状態を「Excelが壊れた」「OnTimeが効かない」と見ると、原因を見誤ります。正しくは、VBAが制御を持ち続けているため、Excel側が別の処理へ進む機会を得られていない、と見るべきです。

1.2 DoEventsは制御を一時的に戻す命令である

DoEvents は、VBAの処理を一時的に中断し、Excel側、より正確にはWindows側のメッセージ処理に制御を渡す命令です。

これにより、Excelは保留中の画面描画、フォーム更新、ユーザー操作、イベント処理などを進める機会を得ます。

ただし、ここで注意が必要です。DoEvents は、Excel内部のすべてのイベントキューが完全に空になるまで待つ命令ではありません。また、イベントを1件だけ処理して戻る命令でもありません。

DoEvents は、Excel側に処理機会を渡す命令です。その結果として、どの処理がどこまで進むかは、その時点のExcelの状態、予約されている処理、ユーザー操作、イベントの状況に左右されます。

第2章 OnTimeは別スレッドで動くわけではない

この章では、Application.OnTime の位置づけを整理します。OnTime は予約実行の仕組みですが、VBA実行中の処理へ別スレッドで割り込む仕組みではありません。

2.1 OnTimeは実行可能になったときに呼ばれる

Application.OnTime は、指定した時刻にプロシージャを実行するための仕組みです。

しかし、OnTime で指定したプロシージャは、別スレッドで並列実行されるわけではありません。Excel VBAの通常の実行環境では、VBA処理、Excelイベント、UserFormイベント、OnTime で呼ばれるプロシージャは、基本的に同じExcelのメインUIスレッド上で動くと考えるのが実務上は自然です。

そのため、あるVBAプロシージャが実行中であれば、OnTime で予約されたプロシージャもすぐには実行されません。OnTime のプロシージャが実行されるのは、Excelがそれを実行できる状態になったときです。

たとえば、現在実行中のマクロが終了したとき、DoEvents によってExcel側に一時的に制御が戻ったとき、またはExcelがReady相当の状態に戻ったときに実行されます。

2.2 DoEvents中にOnTimeが実行されるイメージ

MainProc の中で OnTime を予約し、その後に DoEvents を実行した場合、概念的には次の流れになります。

MainProc 実行中
  ↓
Application.OnTime で OnTimeProc を予約
  ↓
DoEvents
  ↓
Excel側のメッセージ処理に制御が戻る
  ↓
予約時刻に到達していれば OnTimeProc が実行される
  ↓
OnTimeProc 終了
  ↓
DoEvents 終了
  ↓
MainProc の続きに戻る

このとき、OnTimeProc は MainProc とは別に横で動いているわけではありません。DoEvents の途中でExcel側に制御が戻り、その中で OnTimeProc が呼ばれます。

スタックのイメージとしては、現在実行中の処理の上に OnTimeProc が乗るような形です。

OnTimeProc
DoEvents
MainProc

OnTimeProc が終了すると DoEvents に戻り、さらに DoEvents が終了すると MainProc の次の行に戻ります。このため、OnTime はマルチスレッドではありませんが、実行中の処理に対する再入に近い性質を持ちます。

第3章 OnTimeの実行確認は自前フラグで見る

この章では、OnTime が本当に実行されたかを観測する方法を整理します。重要なのは、Excel内部の全キュー完了を見るのではなく、自分が待ちたい処理が実行された事実を見ることです。

3.1 Application.Readyだけでは目的に合わない

Excelには、Excel内部のイベントキューがすべて処理されたことを直接示す実用的な公式フラグはありません。

Application.Ready は近い性質を持ちますが、これはExcelが準備完了状態かどうかを見るプロパティです。Ready が True であっても、こちらが待っている OnTime が実行済みとは限りません。また、Ready が True だからといって、UserFormの描画、Excel内部の遅延処理、すべてのイベント処理が完全に終わったとも言えません。

したがって、次のようなコードは、目的に対して弱い書き方です。

Do While Not Application.Ready
    DoEvents
Loop

これは、ExcelがReadyになるまで待つコードです。OnTime が実行されたことを待つコードではありません。

3.2 OnTime側でフラグを立てる

OnTime が本当に実行されたかを確認したい場合は、自分でフラグを用意するのが自然です。

考え方は、次のとおりです。

  1. Main側でグローバル変数を False にする。

  2. Application.OnTime でコールバックを予約する。

  3. Main側で DoEvents を回す。

  4. OnTime 側のプロシージャでグローバル変数を True にする。

  5. Main側がその変更を見て、OnTime が実行されたと判断する。

最小限のコードで表すと、次のようになります。

Private g_OnTimeDone As Boolean
Sub MainProc()
    g_OnTimeDone = False
    Application.OnTime Now, "OnTimeCallback"
    Do Until g_OnTimeDone
        DoEvents
    Loop
End Sub
Sub OnTimeCallback()
    g_OnTimeDone = True
End Sub

このコードの意味は、DoEvents を何回実行したかではありません。OnTime で予約したプロシージャが実際に呼ばれたかを見ている、ということです。

3.3 これはイベントキュー完了待ちではない

ここは特に正確に書く必要があります。

この方法は、Excelのイベントキューが完全に空になったことを確認する方法ではありません。そうではなく、Excelが OnTime で予約されたプロシージャを実行できる状態になり、実際にそのプロシージャが呼ばれたことを確認する方法です。

つまり、OnTime を一種のプローブとして使っています。

DoEvents でExcel側に制御を戻します。Excelが OnTime を実行できる状態なら、OnTimeCallback が呼ばれます。OnTimeCallback の中でフラグを立てます。Main側は、そのフラグを見て、少なくとも OnTime が実行されるところまではExcel側の処理が進んだと判断します。

第4章 実務コードではタイムアウトと再入防止を入れる

この章では、上の考え方を実務コードとして使うときの注意点を整理します。DoEvents は便利ですが、単なる待機命令ではありません。

4.1 無限ループにしてはいけない

単純に次のようなコードを書くのは危険です。

Do Until g_OnTimeDone
    DoEvents
Loop

OnTimeCallback が何らかの理由で実行されなければ、無限ループになります。

たとえば、OnTime の指定プロシージャ名が間違っている、ブックやモジュールの状態によりプロシージャを呼べない、Excelが実行可能状態に戻らない、別の処理やモードが残っている、OnTime 側がエラーで正常終了していない、というケースがあり得ます。

そのため、実務コードでは必ず最大ループ回数、タイムアウト時間、再入防止フラグを入れます。

考え方としては、次の形です。

Do
    DoEvents
    If g_OnTimeDone Then Exit Do
    If Timer - startTime > 5 Then Exit Do
Loop

フラグが立つまで待つことが基本ではありますが、永久に待つ設計にしてはいけません。

4.2 DoEventsは再入の入口になる

DoEvents を呼ぶと、Excel側に制御が戻ります。その間に、OnTime で予約されたプロシージャ、UserFormのイベント、ボタン押下イベント、シートイベント、ブックイベント、ユーザー操作、別のマクロ実行などが動く可能性があります。

これは別スレッドの競合ではありません。しかし、再入による状態破壊に近い問題を起こすことがあります。

たとえば、MainProc が処理中のつもりで保持しているグローバル変数やシート状態を、DoEvents 中に呼ばれた OnTimeProc やイベント処理が変更してしまうことがあります。

そのため、今回のような用途では、OnTime 側の処理はフラグを立てるだけにしておくのが安全です。

Sub OnTimeCallback()
    g_OnTimeDone = True
End Sub

シート更新、フォーム更新、別マクロ呼び出し、大量処理などは、Main側に戻ってから行う方が安全です。

4.3 実務用の最小形

実務では、次のように上限付きで待つ形にします。ここでは考え方を示すための最小例にしています。

Private g_OnTimeDone As Boolean
Private g_WaitingOnTime As Boolean
Sub MainProc()
    Dim startTime As Single
    If g_WaitingOnTime Then Exit Sub
    g_WaitingOnTime = True
    g_OnTimeDone = False
    startTime = Timer
    Application.OnTime Now, "OnTimeCallback"
    Do
        DoEvents
        If g_OnTimeDone Then Exit Do
        If Timer - startTime > 5 Then Exit Do
    Loop
    g_WaitingOnTime = False
    If Not g_OnTimeDone Then
        Err.Raise vbObjectError + 1000, , "OnTime callback was not executed within timeout."
    End If
End Sub
Sub OnTimeCallback()
    g_OnTimeDone = True
End Sub

この例の主眼は、OnTime を待つことそのものではありません。待ち合わせの完了条件を、Excel内部の見えない状態ではなく、自分が予約したコールバックの実行事実に置くことです。

第5章 この方式の位置づけ

この章では、この記事全体の判断軸をまとめます。DoEvents と OnTime を使ったフラグ確認は便利ですが、過大評価してはいけません。

5.1 同期ではなく観測である

この方式は、厳密な意味でのスレッド同期ではありません。また、Excel内部の全イベント処理完了を保証するものでもありません。

正しい位置づけは、次のとおりです。

DoEventsによってExcel側に制御を戻し、
OnTimeで予約されたプロシージャが実行されたことを、
グローバル変数の変化によって観測する方法

Excelの見えない内部状態を直接見るのではありません。自分が予約した OnTime プロシージャが実際に実行されたかを見る方法です。

この意味では、VBAで扱える範囲ではかなり自然な待ち合わせ方法です。ただし、これをExcelのイベントキューが空になったことの証明として扱ってはいけません。

5.2 設計上の判断

実務上の設計としては、次の考え方が妥当です。

  1. Excel内部のイベントキューが空になったかを直接見ようとしない。

  2. 自分が待ちたい処理が実際に実行されたかを、自前フラグで見る。

  3. OnTime 側ではフラグだけを立てる。

  4. Main側では DoEvents を上限付きで回し、そのフラグが立ったら抜ける。

  5. フラグが立たない場合を正常系から外し、タイムアウトとして扱う。

  6. DoEvents 中の再入で状態が壊れないように、処理中フラグや責務分離を入れる。

OnTime と DoEvents の関係は、並列処理ではなく、Excelの同じ実行系の中で制御を戻すタイミングの問題です。この理解に立つと、Application.Ready だけに頼らず、自分が確認したい完了条件をコード上の事実として持つ設計にできます。

まとめ

DoEvents は、VBAの処理を少し止めて、Excel側へ制御を戻す命令です。OnTime は、指定時刻になったら別スレッドで走る命令ではなく、Excelが実行可能なタイミングで呼ばれる予約実行です。

この2つを組み合わせると、DoEvents 中に OnTime のコールバックが呼ばれ、そのコールバックで立てたフラグをMain側が見る、という形で実行確認ができます。

ただし、これはExcel内部の全イベント処理が完了した証明ではありません。確認できるのは、予約した OnTime プロシージャが実際に実行されたという事実です。

したがって、VBAで安全寄りに設計するなら、OnTime 側ではフラグだけを立て、Main側では DoEvents をタイムアウト付きで回します。そして、再入によって状態が壊れないように、処理中フラグや責務分離を入れます。

この考え方が、OnTime と DoEvents の性質を利用した、VBAとして現実的な待ち合わせ設計です。

出典メモ

  • 元原稿: C:\Users\hoehoe\Downloads\OnTime_DoEvents_関係整理.md

  • 取得日: 2026-06-01

  • 入力元種別: Downloads Markdown

  • 編集方針: 元原稿の主張を維持し、LWP記事標準の冒頭構成、章立て、品質ゲート、実務上の注意を補った。