【テクニカル・上級編】実務中級者向け:VB.NETでの「System.IO.FileStream」と「FileShare」を活用した排他制御:別プロセスでロックされたファイルの安全な読み書き手法 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

業務自動化の最前線で戦う諸君なら、一度は「System.IO.IOException: プロセスはファイルにアクセスできません。別のプロセスが使用中です。」という絶望的なエラーメッセージに直面したことがあるはずだ。

特に、レガシーな基幹システムが吐き出す巨大なCSV、誰かが開きっぱなしにしている共有サーバー上のExcel、あるいはリアルタイムで更新され続けるログファイル。これらを「安全に、かつ確実に」読み取ることは、単なるコーディングスキルを超えた、Windows OSのファイルシステムとリソース管理に対する深い洞察が求められる領域である。

今回は、VB.NETにおける`System.IO.FileStream`と`FileShare`列挙体を使いこなし、他プロセスの占有をすり抜けながらデータの整合性を保つ「極限のファイル操作」について解説する。

1. なぜ「File.ReadAllLines」は現場で通用しないのか

初心者は往々にして `File.ReadAllLines` や `File.ReadAllText` を多用する。しかし、これらのメソッドは内部的にファイルをオープンする際、共有モードを厳格に制限する。

もし対象のファイルが他のアプリケーション(例えばExcelや、追記中のロガー)によって書き込みロックされている場合、これらのメソッドは即座に例外をスローして停止する。業務自動化において「相手がファイルを閉じるまで待つ」という設計は、デッドロックやタイムアウトの温床であり、三流の仕事と言わざるを得ない。

我々プロフェッショナルが手に取るべきは、`System.IO.FileStream`による低レイヤーな制御である。

2. FileShare列挙体:OSへの「共有の宣言」

`FileStream`をインスタンス化する際、最も重要な引数が `FileShare` だ。これは、自分がファイルを開いている間に「他者がそのファイルに対して何をすることを許すか」をWindowsカーネル(CreateFile API)に伝えるためのフラグである。

  • FileShare.None: 他のプロセスはいかなるアクセスもできない。完全な独占。
  • FileShare.Read: 他のプロセスは読み取りのみ可能。
  • FileShare.Write: 他のプロセスは書き込みのみ可能。
  • FileShare.ReadWrite: 他のプロセスが読み書き両方を行うことを許可する。

「別プロセスが書き込み中のログを読み取る」というミッションにおいて、正解は `FileShare.ReadWrite` を指定することだ。これにより、相手が書き込みのためにファイルを開いていても、OSは「この読み取りプロセスは、他者が書き込むことを許容している」と判断し、ハンドルを貸し出してくれる。

3. 実践:ロックを回避する堅牢な読み取り実装

以下に、実務でそのまま転用できる「リトライロジック付き・共有モード読み取り」のテンプレートを示す。

.net
Imports System.IO
Imports System.Text
Imports System.Threading

”’

”’ ファイルの排他制御を潜り抜け、安全にデータを取得するためのアーキテクチャ
”’

Public Class FileAccessArchitect

”’

”’ ロックされたファイルを共有モードで強制的に読み込む
”’

”’ 対象ファイルのパス ”’ ファイル内容の文字列
Public Function ForceReadLockedFile(filePath As String) As String
‘ リトライ回数と待機時間は業務要件に合わせて調整せよ
Const MaxRetries As Integer = 5
Const DelayMilliseconds As Integer = 1000
Dim lastException As Exception = Nothing

For i As Integer = 1 To MaxRetries
Try
‘ FileStreamを明示的に生成。
‘ FileMode.Open: 既存ファイルを開く
‘ FileAccess.Read: 自身の権限は読み取りのみ
‘ FileShare.ReadWrite: 他プロセスの読み書きを一切制限しない(重要)
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using reader As New StreamReader(fs, Encoding.GetEncoding(“Shift_JIS”))
Return reader.ReadToEnd()
End Using
End Using

Catch ex As IOException
‘ ファイルが完全にロック(FileShare.Noneで開かれている等)されている場合
lastException = ex
Debug.Print($”Attempt {i}: ファイルがロックされています。再試行中…”)
Thread.Sleep(DelayMilliseconds)
End Try
Next

Throw New Exception($”ファイル {filePath} の読み取りに失敗しました。プロセスが完全に占有されています。”, lastException)
End Function

End Class

このコードの急所

1. Usingブロックの徹底: `FileStream` はアンマネージドリソース(ファイルハンドル)を保持する。`Using` を抜けた瞬間に `Dispose` が呼ばれ、OSにハンドルを返却することを保証しなければならない。これを怠る者は、自らが「ファイルをロックする元凶」となる。
2. FileShare.ReadWrite: これが今回の肝だ。たとえ他プロセスが `FileShare.Read` で開いていても、こちらが `ReadWrite` を許容していれば衝突は回避できる。
3. Encodingの明示: 日本のレガシーシステムは未だに `Shift_JIS` (CP932) の世界だ。デフォルトの `UTF-8` で読み込むという過ちは、データ破壊と同義である。

4. Windows API `CreateFile` の影を意識せよ

.NETの `FileStream` は、最終的に `kernel32.dll` の `CreateFile` 関数を呼び出している。Windowsのファイルシステムレベルでは、ファイルを開く際に「アクセス権(Access Mask)」と「共有モード(Share Mode)」の論理積が計算される。

もし、先行するプロセス(例えば一部の行儀の悪い古いツール)が `dwShareMode = 0` (FileShare.None) でファイルを開いている場合、後続の我々がどれほど `FileShare.ReadWrite` を叫んだところで、OSはアクセスを拒否する。

その場合、唯一の対抗策は「リトライ」である。相手がハンドルを放す一瞬の隙を突く。上記のコードにリトライロジックを組み込んでいるのは、それが単なる理想論ではなく、泥臭い現場の真実だからだ。

5. メモリ最適化とパフォーマンスの極致

巨大なログファイルを扱う場合、`ReadToEnd()` は避けるべきだ。メモリ(Managed Heap)を一度に数百MB消費し、GC(Garbage Collector)に過度な負荷をかける。

.net
‘ 巨大ファイルはバッファリングしながら一行ずつ処理する
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using reader As New StreamReader(fs, Encoding.GetEncoding(“Shift_JIS”))
While Not reader.EndOfStream
Dim line As String = reader.ReadLine()
‘ ここで一行ずつ処理。メモリ消費を一定に保つ。
End While
End Using
End Using

この「一行ずつ(Stream-based)」の処理こそが、エンタープライズ級のバッチ処理において求められる「枯れた技術」の真髄である。

結論:アーキテクトとしての矜持

VB.NETという言語は、しばしば初心者向けと揶揄される。しかし、その背後にある.NET FrameworkおよびWindows OSの機構を理解し、`FileStream` の一引数にまで魂を込めることができれば、これほど強力な武器はない。

「ファイルが開けない」という些細なトラブルで止まらない、強靭な自動化システム。それを作るのは、言語の仕様書をなぞるだけのプログラマではなく、OSと対話し、リソースのライフサイクルを完全に掌握するチーフアーキテクトである諸君だ。

このレシピを自身のライブラリに加え、現場の「動かない」を「止まらない」に変えてほしい。

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