【実務・中級編】【上級者向け】マルチユーザー環境での同時編集競合を回避するファイル排他制御 – Project VBA解析バイブル

スポンサーリンク

【上級者向け】マルチユーザー環境での同時編集競合を回避するファイル排他制御

プロジェクトマネジメントにおいて、複数のメンバーが同じスケジュールファイルを共有フォルダ上で同時に操作するシチュエーションは、現場のインフラ担当者にとって悪夢の始まりだ。

「保存しようとしたら他のユーザーに上書きされた」
「知らずに開いてしまい、自分の変更が消失した」

MS Projectの標準機能や素朴なVBAマクロに甘えていると、こうした致命的なコンフリクト(競合)が容赦なく発生し、プロジェクトのデータ整合性は一瞬で崩壊する。

今回は、Project VBAのライフサイクルとWindowsのファイルシステムを熟知したアーキテクトだけが知る、「安全かつ確実なファイル排他制御(Exclusive Control)」の極意を伝授する。甘い実装で現場を絶望させるのは、今日で終わりにしよう。

なぜ「単純なエラートラップ」では防げないのか?

多くの初級プログラマブル・エンジニアは、ファイルを保存する際に以下のような安易なコードを書く。

‘ 【アンチパターン】単にエラーをトラップするだけの危険な実装
On Error GoTo ErrorHandler
FileSave
Exit Sub
ErrorHandler:
MsgBox “保存に失敗しました。”

これは最悪の設計だ。
なぜなら、「保存ボタンを押した瞬間」に初めて競合が発覚するためである。ユーザーが何時間もかけて入力したデータが、保存の土壇場でロストするリスクを放置していることになる。さらに言えば、MS Project自体の排他制御ダイアログがモーダル表示され、バックグラウンドの自動化プロセスが完全にフリーズする原因にもなる。

真のプロフェッショナルであれば、「ファイルを開く前(あるいは編集開始時)」にロックを検知し、安全にプロセスを分岐させるロジックを構築しなければならない。

堅牢な排他制御を実現する3つのアプローチ

マルチユーザー環境で競合を完全に回避するためには、以下のいずれかの戦略をとる必要がある。

1. ファイルシステムレベルの独占オープン(API活用)
2. 専用のロックファイル(.lck)によるセマフォ制御
3. MS Project標準の読み取り専用チェック

今回は、最も確実で実務への導入ハードルが低い「専用ロックファイルを用いたセマフォ制御」を採用する。共有フォルダ上にダミーのロックファイルを生成・監視させることで、MS Project本体が悲鳴を上げる前に、VBA側で优雅にアクセスを制御するのだ。

【実装コード】プロダクション品質の排他制御モジュール

以下のコードは、エラーハンドリング、ファイルシステムの競合回避、そして安全なライフサイクル管理を網羅した、そのまま現場で使えるプロダクションコードである。

適当な標準モジュールに貼り付けて利用してほしい。

Option Explicit

‘ 宣言:FileSystemObjectの遅延バインディング用定数等
Private Const LOCK_FILE_NAME As String = “~lock_project.lck”

‘ ==============================================================================
:マルチユーザー環境対応 プロジェクト安全オープン&排他制御メイン処理
‘ ==============================================================================
Public Sub SafeOpenProjectWithLock()
Dim targetPath As String
Dim lockFilePath As String
Dim fso As Object
Dim fileNum As Integer

‘ 対象のプロジェクトファイルパス(環境に合わせて変更してください)
targetPath = “C:\ProjectData\MasterSchedule.mpp”
lockFilePath = CreateObject(“Scripting.FileSystemObject”).GetParentFolderName(targetPath) & “\” & LOCK_FILE_NAME

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 1. 対象ファイルが存在するかチェック
If Not fso.FileExists(targetPath) Then
MsgBox “指定されたプロジェクトファイルが存在しません。” & vbCrLf & targetPath, vbCritical, “致命的エラー”
Exit Sub
End If

‘ 2. すでにロックファイルが存在するか(誰かが作業中か)のチェック
If fso.FileExists(lockFilePath) Then
If IsLockFileStale(lockFilePath, fso) Then
‘ ゾンビロック(異常終了時の残骸)と判定して強制削除
On Error Resume Next
fso.DeleteFile lockFilePath, True
On Error GoTo 0
Else
MsgBox “現在、他のユーザーがプロジェクトを編集中です。” & vbCrLf & _
“競合を防ぐため、処理を中断します。” & vbCrLf & _
“ロックファイル: ” & lockFilePath, vbExclamation, “排他制御アラート”
Exit Sub
End If
End If

‘ 3. 排他ロックの取得(排他モードでファイルを作成)
On Error GoTo LockError
fileNum = FreeFile
Open lockFilePath For Output Access Write Lock Read Write As #fileNum
‘ ロックファイルに自身の識別情報(PC名とユーザー名)を書き込んでおく
Print #fileNum, “Locked by: ” & Environ(“USERNAME”) & ” on ” & Environ(“COMPUTERNAME”) & ” at ” & Now
Close #fileNum
On Error GoTo 0

‘ 4. 安全にプロジェクトを開く
On Error GoTo OpenError
FileOpenEx Name:=targetPath, ReadOnly:=False

MsgBox “排他ロックを取得し、プロジェクトを正常に開きました。”, vbInformation, “成功”

‘ ※この後、アプリケーション終了時やファイルクローズ時に必ず SafeCloseProject を呼ぶこと
Exit Sub

LockError:
MsgBox “ロックファイルの生成に失敗しました。ネットワークの接続を確認してください。”, vbCritical, “排他エラー”
Exit Sub

OpenError:
‘ 開けなかった場合はロックを解放
Close #fileNum
If fso.FileExists(lockFilePath) Then fso.DeleteFile lockFilePath, True
MsgBox “プロジェクトファイルのオープンに失敗しました。”, vbCritical, “オープンエラー”
End Sub

‘ ==============================================================================
‘ 安全なクローズとロックファイルの解放
‘ ==============================================================================
Public Sub SafeCloseProject()
Dim targetPath As String
Dim lockFilePath As String
Dim fso As Object

targetPath = “C:\ProjectData\MasterSchedule.mpp”
Set fso = CreateObject(“Scripting.FileSystemObject”)
lockFilePath = fso.GetParentFolderName(targetPath) & “\” & LOCK_FILE_NAME

‘ プロジェクトを保存して閉じる
On Error Resume Next
ActiveProject.Save
FileCloseEx pjSaveNo
On Error GoTo 0

‘ 自信が生成したロックファイルを削除
If fso.FileExists(lockFilePath) Then
fso.DeleteFile lockFilePath, True
End If

MsgBox “プロジェクトを安全に保存・終了し、ロックを解放しました。”, vbInformation, “終了処理完了”
End Sub

‘ ==============================================================================
‘ 補助関数:ゾンビロック(放置されたロックファイル)の判定
‘ ==============================================================================
Private Function IsLockFileStale(ByVal lockPath As String, ByRef fso As Object) As Boolean
Dim fileObj As Object
Set fileObj = fso.GetFile(lockPath)

‘ 例:最終更新から4時間以上経過している場合は、クラッシュ等によるゴーストとみなす
If DateDiff(“h”, fileObj.DateLastModified, Now) > 4 Then
IsLockFileStale = True
Else
IsLockFileStale = False
End If
End Function

アーキテクトが解説する実装の急所

このコードが他の凡庸なスクリプトと一線を画す理由は以下の3点だ。

1. `Open … For Output Lock Read Write` によるハードロック

単にテキストファイルを作るだけでなく、VBAのネイティブなファイル入出力命令で「読み書き両方をロック(Lock Read Write)」してファイルハンドルを掴んでいる。これにより、ミリ秒単位のタイミングで他のユーザーと同時にマクロが走った場合でも、Windowsのファイルシステムレベルで競合を弾くことが可能になる。

2. ゾンビロック(ゴースト)への耐性

ネットワーク環境では、ユーザーがPCを強制終了したり、VBAが予期せぬエラーで落ちたりすることで、ロックファイルが永遠に残る「ゾンビ化」が頻発する。上記の `IsLockFileStale` 関数では、「最後の更新から一定時間(例: 4時間)経過したロックは無効とみなして強制突破する」という現場の運用に耐えうるフェイルセーフを実装している。

3. オブジェクトライフサイクルとの完全な同期

ファイルを開く処理とロックの取得、そして閉じる処理とロックの解放(`SafeCloseProject`)が厳密にペアになっている。これにより、リソースリークや「開いたはいいが二度と開けなくなった」という致命的なデッドロック状態を物理的に排除している。

まとめ

業務自動化において、「動けばいいや」で作られたコードは、やがて組織全体を巻き込むシステム障害へと成長する。特にマルチユーザー環境を伴うProject VBAの開発では、ファイルシステムへの配慮と排他制御のロジックがそのエンジニアの格を決める。

今回紹介したセマフォ制御の思想をあなたのシステムにも組み込み、コンフリクトの恐怖から解放された強靭なプロジェクト基盤を構築してほしい。

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