MS Project VBAの限界を突破する:非同期処理のシмуляションとUIフリーズ完全回避術
大規模なプロジェクト群のバッチ処理、数十に及ぶサブプロジェクトの動的リンク、あるいは数万件のタスクに対する一括カスタムフィールド書き込み。MS ProjectのVBAを用いた開発において、避けて通れない壁が「UIの完全な硬直(フリーズ)」と「Windowsメッセージキューの飽和」である。
Excel VBAであれば `Application.ScreenUpdating` や `Calculation` の制御で一定の回避ができるが、MS ProjectのCOMサーバーはシングルスレッドの制約がより厳格であり、重い処理を流し込んだ瞬間にタスクマネージャーは「応答なし」を表示し、OSからの強制終了(Ghosting)の恐怖に怯えることになる。
今回は、Project VBAの底知れぬ挙動を知り尽くしたアーキテクトへ向けて、Windows APIと協調した非同期処理のシミュレーション、メモリの極限最適化、そしてユーザーフレンドリーな進捗UIの構築手法を、実戦投入可能なコードとともに解き明かす。
—
1. MS Projectにおける「フリーズ」の根本原因とアーキテクチャの罠
なぜProject VBAは重いのか。それは、VBAコードの実行スレッドと、MS Projectの描画・計算エンジン(Calculation Engine)が完全に同一のスレッドで動いているからだ。
数千件のタスクに対して `.Duration` や `.Start` をループで変更すると、その都度、CPM(クリティカルパス法)の再計算スケジュールが走る。これがUIスレッドを完全に占有し、Windowsからの `WM_PAINT` やユーザーからのマウス・キーボード入力を処理できなくなる。
これを解決するには、以下の3つのアプローチを統合する必要がある。
1. 計算モードの手動化 (`Calculation = manual`)によるスケジューリングの遅延評価
2. `DoEvents` の適切な挟み込みによるメッセージキューの解放
3. 高精度タイマー(Windows API)を用いた処理負荷の動的スロットリング
—
2. 実装:高精度進捗表示とUIフリーズ回避エンジン
以下に、大量のプロジェクト(またはタスク)を安全に処理しつつ、モデルレスフォームで滑らかな進捗バーとキャンセル機能を提供する実用コードを示す。
準備:モデルレスユーザーフォーム (`frmProgress`) の要件
- フォーム上にラベル (`lblStatus`) とプログレスバー (`Bar`) を配置すること。
- キャンセル用グローバルフラグ、またはクラス変数を設けること。
標準モジュール:非同期制御の中核ロジック
Option Explicit
‘ Windows API: 実行スレッドをブロックせずにCPUに他の処理を譲る、または時間計測用
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function GetTickCount Lib “kernel32″ () As Long
‘ キャンセル状態を管理するフラグ(必要に応じてフォーム側から参照・変更)
Public g_CancelRequested As Boolean
”’
”’
Public Sub ExecuteHeavyProjectBatch()
Dim prj As Project
Dim lngTotalTasks As Long
Dim lngProcessed As Long
Dim tStart As Long
‘ 1. 環境の最適化(描画と自動計算の停止による圧倒的なパフォーマンス向上)
Application.ScreenUpdating = False
Application.Calculation = pjCalculationManual
g_CancelRequested = False
lngProcessed = 0
‘ 進捗フォームのロードと表示(モデルレス)
Load frmProgress
frmProgress.Show vbModeless
frmProgress.Caption = “バッチ処理実行中…”
tStart = GetTickCount()
On Error GoTo ErrorHandler
‘ 例として開いている全プロジェクトを走査
For Each prj In Application.Projects
If prj.ReadOnly = False Then
‘ プロジェクト内のタスクに対する重い処理をシミュレート
lngTotalTasks = prj.Tasks.Count
Dim t As Task
For Each t In prj.Tasks
If Not t Is Nothing Then
‘ — 【コア処理のシミュレーション】 —
‘ 例:カスタムフィールドへの書き込みと属性変更
t.Text1 = “Processed by Arch-Engine”
‘ t.Cost = t.Cost 1.05 など
‘ ————————————
lngProcessed = lngProcessed + 1
‘ 100件に1度、あるいは一定時間経過ごとにUIへ制御を返却
If lngProcessed Mod 50 = 0 Then
‘ フォームの進捗更新
frmProgress.UpdateProgress lngProcessed, lngTotalTasks, prj.Name
‘ 【最重要】Windowsメッセージキューを処理し、フリーズを防ぐ
DoEvents
‘ ユーザーがキャンセルボタンを押した場合の離脱処理
If g_CancelRequested Then
If MsgBox(“処理が中断されました。変更を破棄しますか?”, vbYesNo + vbExclamation, “確認”) = vbYes Then
Err.Raise 9999, “BatchProcess”, “ユーザーによる強制中断”
End If
End If
‘ 過剰なCPU占有を防ぐためのマイクロウェーブ・スリープ(必要に応じて調整)
‘ Sleep 1
End If
End If
Next t
5. CleanReference prj ‘ オブジェクトの明示的解放
Next prj
MsgBox “バッチ処理が正常に完了しました。”, vbInformation, “完了”
GoTo Finally
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Finally:
‘ 6. 環境の復元(絶対に忘れてはならない)
Application.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
‘ プロジェクト全体の再計算を明示的に実行
Application.CalculateAll
Unload frmProgress
CleanReference frmProgress
End Sub
”’
”’
Private Sub CleanReference(ByRef targetObj As Object)
On Error Resume Next
Set targetObj = Nothing
On Error GoTo 0
End Sub
—
3. シニアエンジニアが知るべき「メモリ最適化」と「COMの罠」
循環参照とメモリリークの温床
Project VBAでは、`For Each prj In Application.Projects` や `For Each t In prj.Tasks` のような暗黙のコレクション走査を行う際、VBAの裏側でCOMラッパーオブジェクトが大量に生成される。
これをそのまま放置すると、VBAのガベージコレクション(RC方式)が追いつかず、メモリリークを起こしてプロセスが突然沈黙する。
対策:
ループ内で一時的に使用したオブジェクト変数(今回のコードにおける `t` や `prj`)は、処理のブロックごとに `Set t = Nothing` で明示的に参照を切るべきである。特に大量のタスクやリソースを扱うループ内では、変数のスコープを極限まで小さくし、ガベージコレクションの負担を軽減させることが鉄則となる。
`DoEvents` の諸刃の剣
UIフリーズを防ぐ唯一の救世主である `DoEvents` だが、これには致命的な副作用がある。
「イベントの再入可能性(Reentrancy)」である。
`DoEvents` が実行された瞬間、ユーザーが再度ボタンをクリックしたり、別のウィンドウ操作を行ったりすることで、同じVBAプロシージャが二重起動されるリスクが生じる。
極限の知見:
バッチ実行中は、フォーム側のボタンを無効化(`Enabled = False`)するか、モジュールレベルのフラグ(`g_IsProcessing` 等)で多重実行を完全にブロックするガード句を必ず実装しなければならない。
‘ 多重実行防止のガード句の例
Static isRunning As Boolean
If isRunning Then Exit Sub
isRunning = True
‘ … 処理 …
isRunning = False
—
4. レガシー環境・システム間連携への応用
この非同期シミュレーション手法は、単なる進捗表示だけに留まらない。
社内基幹システム(SAPや独自ERP)からREST API経由で数千件の進捗データを取得し、それをMS Projectへ流し込むようなエンタープライズ・システム間連携において真価を発揮する。
ネットワークI/OやDBアクセスが絡むバッチ処理は、ミリ秒単位で予測不可能な遅延が発生する。高精度タイマーと `DoEvents` を組み合わせた非同期ループを構築しておくことで、外部システムからの応答待ち(タイムアウト検知など)を挟みつつ、MS Project側のUIを完全に健全な状態に保つことが可能となる。
—
5. 総括
Project VBAにおける非同期処理のシミュレーションは、単なる「画面をフリーズさせないためのテクニック」ではない。それは、OSのメッセージループとMS ProjectのシングルスレッドCOMエンジンとの高度な調停作業である。
- 描画と自動計算の徹底的な抑制
- 適切な粒度での `DoEvents` によるイベントループの解放
- 徹底したオブジェクトのライフサイクル管理とメモリ解放
これらをマスターした時、あなたの書くVBAコードは、もはや単なる「マクロ」の域を超え、堅牢なエンタープライズ・オートメーション・システムへと昇華する。現場のエンジニアよ、その手でレガシーの限界を突破せよ。
