Access VBAにおける「擬似マルチスレッド」の深淵:TimerIntervalが制御する非同期処理のアーキテクチャ
Access VBAはシングルスレッドの制約下にあります。しかし、システムの現場において「重い処理の裏でステータス監視を行いたい」「API連携のポーリングをメイン画面を止めずに行いたい」という要求は、避けて通れない現実です。
多くのエンジニアはここで「DoEvents」を乱用し、スタックオーバーフローや予期せぬ再入(Re-entrancy)によるクラッシュを招きます。真のアーキテクトが選ぶのは、Accessのイベントループそのものをハックする`TimerInterval`と`OnTimer`による非同期タスク・スケジューリングです。
—
1. なぜ「TimerInterval」なのか:イベントループの掌握
Accessの`TimerInterval`は、Windowsのメッセージキュー内にWM_TIMERメッセージを注入します。これを活用することは、メインスレッドを殺さずに「タスクを断片化」して実行することに他なりません。
重要なのは、「タイマー処理内で処理を完結させない」ことです。処理を小さなチャンク(塊)に分け、状態管理変数(ステートマシン)で制御することで、見かけ上のマルチスレッドを実現します。
実践:非同期タスク・スケジューラの骨格
以下のコードは、バックグラウンドでのAPIステータス確認を想定した、メモリリークを排除した設計パターンです。
‘ フォームモジュール内に実装するステートマシン
Option Explicit
Private m_IsProcessing As Boolean
Private m_TaskState As Long
‘ フォーム開示時にタイマーを起動
Private Sub Form_Open(Cancel As Integer)
Me.TimerInterval = 1000 ‘ 1秒間隔でポーリング
m_TaskState = 0
End Sub
Private Sub Form_Timer()
‘ 二重起動防止のガード句
If m_IsProcessing Then Exit Sub
‘ 処理の断片化(ステートマシンによる制御)
Select Case m_TaskState
Case 0: ‘ 準備フェーズ
m_IsProcessing = True
m_TaskState = 1
Case 1: ‘ 非同期API呼出/データ検証
Call ExecuteBackgroundTask
m_TaskState = 2
Case 2: ‘ クリーンアップと終了
m_IsProcessing = False
m_TaskState = 0
End Select
End Sub
Private Sub ExecuteBackgroundTask()
‘ ここに重い処理を記述
‘ 注意: 必ずエラーハンドラを実装し、m_IsProcessingを確実にFalseに戻すこと
On Error GoTo Err_Handler
‘ 例:API連携のダミーコード
‘ Call ExternalSystemSync
Exit Sub
Err_Handler:
m_IsProcessing = False
Debug.Print “Error: ” & Err.Description
End Sub
—
2. メモリ最適化とオブジェクトライフサイクルの厳格な管理
Accessにおける最大の敵は、暗黙的なオブジェクトの参照保持によるメモリリークです。特にタイマー内でCOMオブジェクト(ADODB.ConnectionやXMLHTTPなど)を扱う場合、適切な解放を行わないと、数時間の稼働でVBAのヒープ領域は断片化し、システムは崩壊します。
- 明示的なNothing代入: スコープを抜ける前に必ず `Set obj = Nothing` を実行し、参照カウントをゼロにする。
- 静的変数の利用: タイマー内では不要なメモリ確保を避けるため、必要な変数はモジュールレベル(Private)で保持し、再利用する。
- Windows APIの活用: 処理が重い場合、`Sleep`関数(kernel32)でCPUを解放するのではなく、処理自体を分割してタイマーに委ねるのがAccess流の「優しさ」です。
—
3. レガシー環境における「再入」への警戒
Access VBAは再入可能です。つまり、タイマー処理中にユーザーがフォームのボタンを押すと、別のイベントが割り込んで実行されます。これがデッドロックの温床です。
アーキテクトの戒律:
1. フラグによる排他制御: `m_IsProcessing` のようなフラグ変数を必ず持ち、タイマー処理の入り口でガードをかける。
2. Application.Echoの活用: 大規模なデータ更新を行う際は `Application.Echo False` を使用し、画面描画を抑制することで、タイマー動作時のパフォーマンスを極限まで引き上げる。
3. CurrentDbの再生成を避ける: `CurrentDb`をタイマー内で頻繁に呼び出すと、内部的な参照カウンタが増大しパフォーマンスを悪化させます。DAO.Databaseオブジェクトをモジュールレベルでキャッシュしてください。
‘ 最適化されたDB参照
Private m_db As DAO.Database
Private Property Get CurrentDB_Cached() As DAO.Database
If m_db Is Nothing Then Set m_db = CurrentDb
Set CurrentDB_Cached = m_db
End Property
—
結論:VBAを「制御」するということ
Accessの`TimerInterval`は、単なる時間計測ツールではありません。これはAccessというシングルスレッドの箱庭の中で、「擬似的な並列性」を創出するための唯一の制御点です。
システム管理者やシニアエンジニアとして求められるのは、フレームワークの制限を「文句」として吐き出すことではなく、その制限をハックして「安定した非同期処理」を実装する技術的矜持です。このコードをベースに、あなたのシステムを堅牢なものへと昇華させてください。
VBAは、書き手次第で、まだどこまでも速く、鋭く進化します。
