Project VBAの深淵:数千タスクをねじ伏せる「段階的制約適用」のアーキテクチャ
MS ProjectのVBAオブジェクトモデルを直接叩く際、最も忌むべきは「計算エンジンの再帰的暴走」だ。数千タスク規模のプロジェクトにおいて、制約条件(ConstraintType)をループで一括設定すれば、APIはタスクが一つ更新されるたびにプロジェクト全体を再計算し、OSをフリーズさせる。
これはコードの質ではなく、アーキテクチャの敗北だ。本稿では、レガシーの呪縛を解き、計算負荷を極限まで制御するための「段階的制約適用」の実装論を伝授する。
—
1. 計算エンジンの「暴走」を物理的に遮断する
Projectの計算エンジンは、制約条件が変更されるたびにクリティカルパスを再構築する。これを防ぐ唯一の解は、`Calculation`モードの完全停止と、APIレベルでのメッセージポンプの制御である。
以下のコードは、大規模処理における標準的なプロテクト・ボイラープレートだ。
‘ 計算エンジンの完全停止とメモリ保護
Public Sub ToggleCalculationEngine(ByVal bActive As Boolean)
With Application
.Calculation = IIf(bActive, pjManual, pjAutomatic)
.ScreenUpdating = bActive
‘ 重大な処理の前には強制的にガベージコレクションを促すAPIを叩く
If Not bActive Then SetForegroundWindow (Application.hWnd)
End With
End Sub
2. 段階的適用:バッチ分割の極意
数千タスクを一度に処理しようとするな。APIは「一括更新(Bulk Update)」を意図して設計されていない。タスクを論理グループ(WBSやフェーズ)に分割し、「再計算の最小単位」で制約条件を適用せよ。
実装戦略:Chunking Processing
タスクを配列に格納し、100タスクごとに`Calculate`を明示的に呼び出す手法が最も安定する。
Public Sub ApplyConstraintsInChunks(ByVal proj As Project, ByVal chunkSize As Integer)
Dim tsk As Task, i As Integer
Dim tskCount As Integer: tskCount = 0
‘ 計算エンジンを停止し、ボトルネックを排除
Application.Calculation = pjManual
For Each tsk In proj.Tasks
If Not tsk Is Nothing Then
‘ 制約条件の適用(例:ASAPへリセット)
tsk.ConstraintType = pjASAP
tskCount = tskCount + 1
End If
‘ 指定チャンクごとに計算エンジンを同期
If tskCount Mod chunkSize = 0 Then
Application.Calculate
DoEvents ‘ Windowsメッセージループのスタックをクリア
End If
Next tsk
‘ 最終計算
Application.Calculate
Application.Calculation = pjAutomatic
End Sub
3. Windows APIによる「メモリの死」の回避
VBAのオブジェクトは参照カウンタ方式だが、大規模な`Task`コレクションをループで回すと、メモリリークが頻発し、Excel/Projectが「応答なし」に陥る。
ここで、`SetForegroundWindow`や`Sleep`(kernel32.dll)を呼び出し、OSに対して明示的にプロセスの優先順位を譲渡するアーキテクチャが有効だ。特に、`DoEvents`だけではメモリスタックの解放が追いつかない環境では、以下のAPIを組み込む。
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As LongPtr)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ ループ内に Sleep(1) を挿入することで、CPUスレッドを解放し
‘ スケジューラの暴走を物理的に抑止する
4. チーフアーキテクトからの提言
大規模システム開発において、VBAは「接着剤」に過ぎない。しかし、その接着剤の粘度を間違えれば、プロジェクト全体が崩壊する。
1. 制約の連鎖を断つ: 可能な限り`ConstraintType`は設定せず、先行・後続タスクの依存関係(Link)のみで制御せよ。制約条件は計算負荷を増大させる最大の要因だ。
2. ログの永続化: エラー発生時にどこで計算が止まったかを特定するため、処理の進捗を別プロセスのテキストファイルに書き出せ。`Debug.Print`はデバッグ用であり、本番環境の指標ではない。
3. オブジェクトの解放: ループ内で`Task`オブジェクトを定義したら、必ず`Set tsk = Nothing`で参照を明示的に切る。数千回繰り返されるオブジェクト生成は、VBAのガベージコレクタを確実に疲弊させる。
VBAで大規模プロジェクトを扱うということは、「いつ計算エンジンに呼吸をさせるか」を設計することと同義である。コードをただ書くのではなく、Projectの内部エンジンと対話せよ。
それが、このレガシー環境を支配する唯一の道だ。
