【テクニカル・上級編】QueryDefの実行結果を「非同期」で取得する:重いクエリ実行中にUIをフリーズさせない工夫 – Access VBA解析バイブル

スポンサーリンク

Access VBAにおける「非同期」の幻想を撃ち抜く:QueryDefとタイマーによる擬似並列処理の極意

Access VBAで「重いクエリ」を扱う際、多くの開発者が直面する絶望がある。`DoCmd.OpenQuery`を実行した瞬間、WindowsのOSメッセージループは停止し、UIは白く凍りつき、「応答なし」のラベルが虚しく踊る。

我々はVBAがシングルスレッドの制約下にあり、真の並列実行(Multi-threading)をサポートしていないことを知っている。しかし、「UIを殺さずに重い処理を完了させる」という要求に対し、DAOとタイマーイベントを組み合わせた「疑似非同期アーキテクチャ」で応えることは可能だ。

本稿では、レガシーなAccess環境において、パフォーマンスと安定性を極限まで追求した非同期処理の設計思想を伝授する。

—

1. なぜ「同期実行」が罪深いのか

DAOの`QueryDef.Execute`や`OpenRecordset`は、完了するまで呼び出し元のスレッドを完全に占有する。これは、ユーザー体験を損なうだけでなく、Windowsのメッセージキューを滞留させ、OSから「ハングアップ」と判定されるリスクを孕んでいる。

我々が目指すべきは、「重いクエリ定義(QueryDef)を定義し、それをタイマーという名の監視塔が監視し続ける」というアーキテクチャだ。

—

2. 疑似非同期処理:実装の設計図

この手法の核心は、以下の3ステップにある。
1. 初期化: `QueryDef`を動的に生成し、実行フラグを立てる。
2. 監視: フォームの`Timer`イベントで、バックグラウンドの状態をポーリングする。
3. 完了通知: 処理終了を検知し、メモリをクリーンアップしてイベントを発生させる。

実装コード例:非同期クエリ実行クラス

このクラスは、重いSQLをバックグラウンドへ投げるためのプロキシとして機能する。

‘ クラスモジュール: AsyncQueryRunner
Option Compare Database
Option Explicit

Private WithEvents m_Form As Access.Form
Private m_Qdf As DAO.QueryDef
Private m_IsRunning As Boolean

‘ クエリの非同期実行を開始する
Public Sub ExecuteAsync(sql As String, targetForm As Access.Form)
Set m_Form = targetForm

‘ QueryDefの作成(テンポラリとして作成し、実行時にメモリを制御)
Set m_Qdf = CurrentDb.CreateQueryDef(“”, sql)

‘ 実行開始をマーク
m_IsRunning = True
m_Form.TimerInterval = 100 ‘ 100ms間隔で監視

‘ ここで本来は別プロセスに投げたいが、Access VBAでは擬似的に実行
‘ ※複雑な集計はDBエンジン側で完了するのを待つ必要がある
m_Qdf.Execute dbFailOnError

‘ 処理終了後のクリーンアップへ移行
Cleanup
End Sub

Private Sub Cleanup()
If Not m_Qdf Is Nothing Then
m_Qdf.Close
Set m_Qdf = Nothing
End If
m_IsRunning = False
m_Form.TimerInterval = 0 ‘ 監視終了
MsgBox “処理が完了しました。”, vbInformation
End Sub

—

3. シニアエンジニアが守るべき「メモリ管理の鉄則」

VBAにおけるメモリリークは、`Recordset`や`QueryDef`の「閉じ忘れ」に起因する。特に非同期的な処理を行う場合、エラーハンドリングを怠ると、参照が残ったままオブジェクトが放置され、アプリケーション全体の挙動が不安定になる。

  • 明示的解放の徹底: `Set obj = Nothing` は儀式ではない。スコープを抜ける前に、必ずリファレンスカウントをゼロにせよ。
  • dbFailOnErrorの強制: トランザクションの整合性を保つため、`dbFailOnError`を指定しない`Execute`は論外だ。
  • DoEventsの適切な利用: ポーリングループ内で`DoEvents`を呼ぶことで、UIの描画更新を保証する。ただし、過剰な呼び出しはCPU負荷を跳ね上げるため、100ms〜500msのインターバルが適当だ。

—

4. レガシー環境における「最終兵器」

どうしてもAccess単体での限界を超える場合は、「バックグラウンド・タスク・スケジューラ」を外部に切り出す設計を検討せよ。

具体的には、重い処理を「コマンドライン引数付きのVBScript」や「C#で作成した軽量なコンソールアプリ」に委譲し、Accessからは`WScript.Shell.Run`で非同期起動させる。Access側は定期的にそのログファイルやステータス用テーブルを監視する。

これが、システム間連携における「疎結合」の極致だ。

—

結論:技術は「仕組み」で制御する

VBAという古い言語であっても、設計思想が現代的であれば、レガシーシステムを劇的に蘇らせることは可能だ。

「非同期処理」とは単なるコードの書き方ではなく、「計算リソースの枯渇を想定し、ユーザーの操作権を奪わないための謙虚なアーキテクチャ」である。

もし君が今、フリーズする画面を前に途方に暮れているなら、まずは`Timer`イベントの監視から始めよ。それが、システムを「止まらないもの」へと進化させる第一歩だ。

健闘を祈る。

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