【実務・中級編】フォームの「TimerInterval」と「OnTimer」で実現する、バックグラウンドでの擬似マルチスレッド処理 – Access VBA解析バイブル

スポンサーリンク

Access VBAで「擬似マルチスレッド」を操る:Timerイベントの極意

Access VBAはシングルスレッドです。重い処理を走らせれば、画面はフリーズし、ユーザーは「壊れた」と判断してタスクマネージャーからAccessを強制終了させるでしょう。

多くの初心者は、ここで「DoEvents」をループ内に乱用し、CPUを焼き尽くす無様なコードを書きます。しかし、プロフェッショナルは違います。Accessが持つ「TimerInterval」という強力なイベントドリブン・エンジンを、非同期処理のスケジューラーとして再定義するのです。

今回は、バックグラウンドでのステータス監視や、長時間のバッチ処理を「メインスレッドを殺さずに」実行する、堅牢なアーキテクチャを伝授します。

なぜ「DoEvents」ループではいけないのか

ループ内で`DoEvents`を呼ぶのは、処理を一時中断してOSに制御を戻す「おまじない」に過ぎません。しかし、これでは処理の進捗管理が難しく、何より「ユーザーがフォームを閉じた瞬間に発生する不正なメモリアクセス」を制御できません。

堅牢な設計の鉄則:

  • イベント駆動を徹底する: 処理を「開始」「継続」「終了」のステートに分解し、Timerイベントをタスクランナーとして使う。
  • 状態保持(State Management): どの処理がどこまで進んだかをフォームのモジュールレベル変数で保持する。
  • エラーの封じ込め: タイマー内で発生したエラーがAccess全体を落とさないよう、厳密な例外処理を記述する。

実装:非同期処理コントローラーの設計

このコードは、フォーム上に配置したタイマーを「疑似的なタスクスイッチャー」として機能させるテンプレートです。

Option Compare Database
Option Explicit

‘ 処理状態を管理する列挙型
Private Enum TaskState
Idle = 0
Processing = 1
Finished = 2
End Enum

‘ モジュールレベル変数で状態を保持
Private m_TaskState As TaskState
Private m_BatchCounter As Long

‘ — 処理開始ボタン —
Private Sub cmdStart_Click()
‘ タイマーを有効化(100ミリ秒ごとにイベント発火)
Me.TimerInterval = 100
m_TaskState = Processing
m_BatchCounter = 0

Debug.Print “バックグラウンド処理を開始しました。”
End Sub

‘ — タイマーイベント:ここが非同期処理の心臓部 —
Private Sub Form_Timer()
‘ 状態がIdleなら何もしない
If m_TaskState = Idle Then Exit Sub

‘ 処理の安全性を確保するためのエラーハンドラ
On Error GoTo ErrorHandler

‘ 1回のタイマーサイクルで「小さな処理」だけを行う(スライス処理)
‘ これによりUIのレスポンスが維持される
m_BatchCounter = m_BatchCounter + 1

‘ 例:100回で完了する重い処理のシミュレーション
If m_BatchCounter <= 100 Then ' ここに実務的な処理(API叩く、DB更新など)を記述 Debug.Print "処理実行中: " & m_BatchCounter & "%" Else ' 完了処理 Me.TimerInterval = 0 ' タイマー停止 m_TaskState = Finished MsgBox "バックグラウンド処理が完了しました。", vbInformation End If Exit Sub ErrorHandler: ' 異常終了時は必ずタイマーを止めて暴走を防ぐ Me.TimerInterval = 0 MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical End Sub ---

プロダクション環境で生き残るための「3つの規律」

このコードを現場に導入する際、以下の3点だけは必ず守ってください。

1. タイマー間隔(Interval)の最適化

100ms(10回/秒)は多くのケースで最適ですが、処理内容が重すぎる場合は500ms〜1000msまで広げてください。CPU使用率を上げすぎると、OS側から「応答なし」と判定されるリスクが増します。

2. DB接続の局所化(CurrentDbの扱い)

`CurrentDb`をタイマー内で何度も呼び出すと、オブジェクトの生成コストが積み重なります。必要であれば、モジュールレベルで`DAO.Database`オブジェクトを保持し、再利用する設計にしてください。ただし、長時間の保持はロックのリスクを伴うため、トランザクションの範囲は最小限に。

3. フォームのライフサイクルを考慮せよ

ユーザーが処理中にフォームを閉じると、タイマーは停止しますが、バックグラウンド処理が中途半端に残ることがあります。`Form_Unload`イベントにて、必ず`Me.TimerInterval = 0`を呼び出し、タスクを強制終了させるガードコードを書いてください。

最後に:エンジニアとしての矜持

VBAはレガシーと言われますが、その特性を理解し、OSのイベントキューを制御下に置くことができれば、驚くほど安定した業務システムを構築できます。

今回紹介した「タイマーによるタスク分割」は、単なる小手先のテクニックではなく、「シングルスレッド環境下でいかに並列性を擬似的に再現するか」という、アーキテクチャの本質に触れる手法です。

さあ、コードを書いて、Accessの限界を突破してください。あなたのツールが、誰かの業務時間を劇的に短縮することを期待しています。

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