【入門編】【エラーハンドリング】「タスクがロックされています」例外を回避するリトライ処理の実装 – Project VBA解析バイブル

スポンサーリンク

みなさん、こんにちは!今日もProject VBAの世界へようこそ。
現場の業務自動化を支える先輩エンジニアとして、みなさんが「マクロの記録」を卒業し、プロのエディションへステップアップするお手伝いをします。

さて、今回のテーマは「タスクがロックされています」という競合例外の回避です。

Microsoft Project(以下、MS Project)をProject OnlineやProject Server、あるいは共有ネットワークフォルダ等の「複数人で同時に触る環境」で運用し始めたとき、必ずと言っていいほど直面するのがこのロックエラー(競合エラー)です。

「自分のパソコンでテストした時は完璧に動いたのに、チームで使ったら突然エラーで止まってしまった…」

そんな苦い経験はありませんか?
大丈夫です!ここをクリアすれば、Project VBAの基礎はバッチリマスターできたと言えますよ。
今回は、エラーに負けない「堅牢なリトライ(再試行)処理」の実装パターンを、基礎からしっかり噛み砕いて解説しますね。

1. なぜ「タスクのロック」が起きるのか?

まずは敵を知ることから始めましょう。なぜVBAコードがタスクを書き換えようとした瞬間にエラーが起きるのでしょうか?

概念図でイメージしてみましょう。

【共有サーバー / Project Online】

├── User A(手動でタスクを編集中) ─── [ロック獲得中]
│ │ (競合発生!)
└── VBAマクロ(書き込みを試行) ────── ❌ [エラー発生!]

MS Projectのオブジェクトモデル(`Application` ➔ `Project` ➔ `Task`)において、タスクは単なるデータの塊ではありません。画面描画やスケジュール再計算エンジンと密接に結びついています。

そのため、以下のようなタイミングでタスクオブジェクトが一時的にロックされます。

1. 他ユーザーが同じプロジェクト/タスクを編集・保存している最中
2. MS Projectのバックグラウンド計算(自動スケジュール調整など)が動いている最中
3. サーバー(PWA等)との同期処理が走っている最中

このロックされている最中にVBAから `Task.PercentComplete = 100` のように値を書き込もうとすると、VBAは「今はこのタスクを触れません!」と悲鳴を上げて実行時エラーを吐いてしまうのです。

2. やってはいけない「ダメな対処法」

初心者の方が最初に取りがちなのが、以下の記述です。

‘ ⚠️ これはキケン!
On Error Resume Next
tsk.PercentComplete = 100
On Error GoTo 0

`On Error Resume Next` でエラーを無視してしまうと、タスクがロックされていた場合に「値が更新されないまま、処理が通り抜けてしまう」ことになります。
結果として、進捗が更新されていないのに「処理完了」と表示され、データの辻褄が合わなくなるという恐ろしい障害に繋がります。

プロのエンジニアは、「エラーを無視する」のではなく、「成功するまで優しく待ってあげる(リトライする)」設計を行います。

3. 解決策:「リトライ+ウェイト」アルゴリズムの設計

ロックは永遠に続くわけではありません。通常は数ミリ秒〜数秒で解除されます。
したがって、以下のようなアルゴリズムを組み込みます。

[タスク更新を試行]

(成功した?) ── YES ──> [完了!次の処理へ]

NO (ロック等のエラー発生)

(規定回数超えた?) ── YES ──> [エラーログを出して安全に中断]

NO

[一定時間待機 (Sleep)]

[DoEventsでOSに制御を返す]

(最初に戻って再試行)

ポイントは「ただループして待つだけでなく、`DoEvents`を呼んでMS ProjectやOSに息抜き(イベント処理)をさせること」です。これにより、バックグラウンドの処理がスムーズに完了し、ロックが解除されやすくなります。

4. 現場でそのまま使える!極限の汎用リトライコード

それでは、具体的なVBAコードを見ていきましょう。
標準モジュールにそのままコピペして使えるように設計してあります。

MS Project VBAにはExcelの `Application.Wait` のような便利な待機関数が標準で用意されていないため、Windows APIの `Sleep` を安全に使用します。

サンプルプログラム:`mod_TaskSafeUpdater.bas`

Option Explicit

‘ ———————————————————————-
‘ Windows APIの Sleep 関数を宣言 (32bit / 64bit 両対応)
‘ ———————————————————————-
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32″ (ByVal dwMilliseconds As Long)
End If

‘ リトライ設定の定数
Private Const MAX_RETRY_COUNT As Long = 5 ‘ 最大再試行回数
Private Const WAIT_INTERVAL_MS As Long = 1000 ‘ 待機時間(ミリ秒): 1秒

”’

”’ 【メイン処理】アクティブプロジェクトの全タスクを安全に進捗更新するサンプル
”’

Public Sub UpdateAllTasksSafely()
Dim prj As Project
Set prj = ActiveProject

‘ プロジェクト内にタスクが存在するかチェック
If prj.Tasks.Count = 0 Then
MsgBox “対象となるタスクが存在しません。”, vbInformation
Exit Sub
End Sub

Dim tsk As Task
Dim successCount As Long
Dim failureCount As Long

‘ 画面描画を一時停止して処理を高速化
ActiveProject.Application.ScreenUpdating = False

On Error GoTo GlobalErrorHandler

For Each tsk In prj.Tasks
‘ サマリータスク(非表示の親タスク)や空白行をスキップ
If Not (tsk Is Nothing) Then
If Not tsk.Summary Then

‘ 堅牢なリトライ関数を呼び出してタスク更新
‘ (例として達成率を50%に変更する処理)
If SetTaskPercentComplete(tsk, 50) Then
successCount = successCount + 1
Else
failureCount = failureCount + 1
‘ 失敗した場合はイミディエイトウィンドウにログを出力
Debug.Print “[FAIL] タスクID: ” & tsk.ID & ” 名称: ” & tsk.Name
End If

End If
End If
Next tsk

‘ 画面描画を再開
ActiveProject.Application.ScreenUpdating = True

‘ 実行結果のフィードバック
MsgBox “更新処理が完了しました。” & vbCrLf & _
“成功: ” & successCount & ” 件” & vbCrLf & _
“失敗: ” & failureCount & ” 件”, IIf(failureCount = 0, vbInformation, vbExclamation)

Exit Sub

GlobalErrorHandler:
ActiveProject.Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

”’

”’ 【コア関数】ロックエラーを回避しながら安全にタスクの進捗率を変更する
”’

”’ 対象のTaskオブジェクト ”’ 設定したい進捗率 (0 – 100) ”’ 更新成功ならTrue、失敗ならFalse
Private Function SetTaskPercentComplete(ByRef targetTask As Task, ByVal newPercent As Integer) As Boolean
Dim attempt As Long
attempt = 0

‘ エラーハンドリングの開始
On Error GoTo LockErrorHandler

TryUpdate:
attempt = attempt + 1

‘ ——————————————————————
‘ [危険領域] ロックされている可能性があるオブジェクト操作
‘ ——————————————————————
targetTask.PercentComplete = newPercent

‘ ここまで到達できれば書き込み成功!
SetTaskPercentComplete = True
Exit Function

LockErrorHandler:
‘ エラー番号の確認(1004等の一般的なCOMエラーや実行時エラーを捕捉)
‘ ※環境やエラー要因に応じて Err.Number を判定することも可能です

If attempt <= MAX_RETRY_COUNT Then ' デバッグログ(開発時の確認用) Debug.Print "タスク [ID:" & targetTask.ID & "] がロックされています。 " & _ "リトライ中... (" & attempt & "/" & MAX_RETRY_COUNT & ")" ' OSに処理を一時譲渡(バックグラウンドのロック解除を促す) DoEvents ' 指定時間(ミリ秒)待機 Sleep WAIT_INTERVAL_MS ' エラー状態をクリアして、試行処理(TryUpdate)へジャンプ Err.Clear Resume TryUpdate Else ' 指定回数リトライしてもダメだった場合 Debug.Print "【エラー】タスク [ID:" & targetTask.ID & "] のロック解除を待機しましたがタイムアウトしました。" Err.Clear SetTaskPercentComplete = False Exit Function End If End Function ---

5. コードの解説とステップアップのコツ

今回のコードで特に重要な「プロのこだわりポイント」を3つ解説します。

① APIの `Sleep` と `DoEvents` の黄金コンビ

`Sleep WAIT_INTERVAL_MS` でVBAの処理を1秒間停止させていますが、その直前にある `DoEvents` が超重要です!
`DoEvents` を呼ぶことで、MS Projectが受け取っている「描画リクエスト」や「サーバーからのレスポンス」を消化する隙(すき)が生まれます。これによってロックが解除される確率が劇的に上がります。

② `On Error GoTo` と `Resume` の正しい組み合わせ

エラー発生時、`Resume TryUpdate` を使うことで「エラーが発生したコードの直前(または指定したラベル)」に安全に復帰できます。
復帰する前には必ず `Err.Clear` を呼んで、エラー状態を綺麗にリセットするのがお約束です。

③ 戻り値(Boolean)で成功・失敗を呼び出し元に伝える

`SetTaskPercentComplete` 関数は、単に更新するだけでなく「成功したか(True)、失敗したか(False)」を返します。
これにより、呼び出し側のメイン処理(`UpdateAllTasksSafely`)で「何件成功して、何件失敗したか」を正確に集計してユーザーに報告できるようになります。

6. まとめ:ここをクリアすればProject VBAの基本はバッチリ!

お疲れ様でした!
一見難しそうに見える「排他制御・競合ハンドリング」ですが、仕組みとパターンさえ覚えてしまえば恐れることは何もありません。

1. エラーを無視(Resume Next)せず、リトライループを作る
2. `DoEvents` と `Sleep` でMS Projectに呼吸をさせる
3. リトライ上限を設けて無限ループを防ぐ

この3原則を守るだけで、あなたの書くProject VBAスクリプトは、現場の誰もが安心して使える「プロ仕様のツール」へと昇華します。

マクロの記録を飛び出して、オブジェクトの挙動をコントロールできるようになると、VBAプログラミングは一気に楽しくなりますよ。ぜひご自身の現場のコードにも組み込んでみてくださいね!

次回も、知っておくと得するProject VBAの極意をお届けします。お楽しみに!

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