C# Tips | ファイル・ディレクトリ操作:ファイルロック判定

C sharp C#
スポンサーリンク

はじめに なぜ「ファイルロック判定」が業務で重要なのか

業務システムでは、「今このファイル、誰かが開いていないかな?」を気にしなければいけない場面がよくあります。
たとえば、次のような状況です。

他のプロセス(Excel、別のバッチ、ウイルススキャンなど)がファイルを掴んでいるときに、こちらも書き込みしようとして失敗する。
ログやインポート用ファイルを上書きしたいのに、「使用中です」と言われて処理が止まる。

こういうときに役立つのが「ファイルロック判定」です。
ただし、C# には「IsLocked みたいなプロパティ」は存在しません。
代わりに、「実際に開いてみて、開けなかったらロックされているとみなす」という考え方をします。

ここでは、プログラミング初心者向けに、「ファイルロックとは何か」「どうやって判定するか」「実務で使えるユーティリティはどう書くか」を、例題付きで丁寧に解説します。


ファイルロックの基本概念をざっくり理解する

「ロックされている」とはどういう状態か

Windows では、ファイルを開くときに「どのように共有するか」を指定できます。
C# の FileStream でいうと、FileShare という列挙体で表現されます。

誰かが「読み書き専用で、共有なし(FileShare.None)」で開いている場合、
他のプロセスはそのファイルを開けません。
この状態を「ロックされている」と感じるわけです。

逆に、「読み取り専用で、読み取り共有あり(FileShare.Read)」で開いている場合は、
他のプロセスも読み取り専用でなら開けます。
この場合、「ロックされている」とは言いにくいです。

つまり、「ロックされているかどうか」は、「こちらがやりたい操作(読み取り・書き込み)ができるかどうか」で決まります。

C# に「ロック状態を直接教えてくれる API はない」

ここが重要なポイントです。
C# には「このファイルはロックされていますか?」と聞く専用のメソッドはありません。

代わりに、「自分がやりたいモードで開いてみて、例外が出たらロックされているとみなす」というパターンを使います。
つまり、「判定=実際に開いてみる」です。


基本パターン 実際に FileStream で開いてみる

書き込み可能かどうかを判定する例

「このファイルに書き込めるかどうか」を知りたい場合の、最もシンプルな判定方法を見てみましょう。

using System;
using System.IO;

public static class FileLockUtil
{
    public static bool IsFileLockedForWrite(string filePath)
    {
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException("ファイルが存在しません。", filePath);
        }

        try
        {
            using (var stream = new FileStream(
                filePath,
                FileMode.Open,
                FileAccess.ReadWrite,
                FileShare.None))
            {
            }

            return false;
        }
        catch (IOException)
        {
            return true;
        }
    }
}
C#

ここで深掘りしたいポイントは三つあります。

一つ目は、FileAccess.ReadWriteFileShare.None を指定していることです。
これは「読み書きしたいし、他の誰とも共有したくない」という意味です。
このモードで開けるなら、「誰も排他的に掴んでいない」と判断できます。

二つ目は、「開けたらすぐ閉じている」ことです。
using ブロックの中で何もしていませんが、「開けるかどうか」を確認するだけなので、これで十分です。

三つ目は、IOException をキャッチして true を返していることです。
他のプロセスにロックされている場合、FileStream のコンストラクタで IOException が投げられます。
それを「ロックされている」と解釈しています。

読み取り可能かどうかを判定する例

「読み取りだけできればいい」という場合は、アクセスモードと共有モードを変えます。

public static bool IsFileLockedForRead(string filePath)
{
    if (!File.Exists(filePath))
    {
        throw new FileNotFoundException("ファイルが存在しません。", filePath);
    }

    try
    {
        using (var stream = new FileStream(
            filePath,
            FileMode.Open,
            FileAccess.Read,
            FileShare.ReadWrite))
        {
        }

        return false;
    }
    catch (IOException)
    {
        return true;
    }
}
C#

ここでは、FileAccess.ReadFileShare.ReadWrite を指定しています。
これは「自分は読み取りだけする。他のプロセスが読み書きしていても共有して構わない」という意味です。

このモードで開けない場合は、「読み取りすらできないほどロックが厳しい」と判断できます。


実務で使える「ファイルロック判定」ユーティリティ

例外を外に漏らさず、true/false だけ返す形

業務コードでは、「ロックされているかどうかを知りたいだけで、例外処理は呼び出し側でやりたい」ということが多いです。
その場合、存在チェックも含めて「全部 bool で返す」ユーティリティにしてしまうのも一つの手です。

using System;
using System.IO;

public static class FileLockUtil
{
    public static bool IsLocked(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
        {
            return false;
        }

        if (!File.Exists(filePath))
        {
            return false;
        }

        try
        {
            using (var stream = new FileStream(
                filePath,
                FileMode.Open,
                FileAccess.ReadWrite,
                FileShare.None))
            {
            }

            return false;
        }
        catch (IOException)
        {
            return true;
        }
        catch (UnauthorizedAccessException)
        {
            return true;
        }
    }
}
C#

ここでは、UnauthorizedAccessException も「ロックされている扱い」にしています。
実際には「権限がない」という別の問題ですが、「今このプロセスからは触れない」という意味では同じなので、まとめて true にしてしまう設計もよくあります。

呼び出し側は、次のようにシンプルに書けます。

string path = @"C:\data\input.csv";

if (FileLockUtil.IsLocked(path))
{
    Console.WriteLine("ファイルが使用中のため、処理をスキップします。");
}
else
{
    Console.WriteLine("ファイルは空いているので、処理を実行します。");
}
C#

リトライ付きで「空くまで待つ」ユーティリティ

業務では、「ロックされていたら少し待ってから再チャレンジしたい」という要件もよくあります。
その場合は、一定回数リトライするユーティリティを用意しておくと便利です。

using System;
using System.IO;
using System.Threading;

public static class FileLockWaiter
{
    public static bool WaitUntilUnlocked(
        string filePath,
        TimeSpan timeout,
        TimeSpan interval)
    {
        DateTime end = DateTime.Now + timeout;

        while (DateTime.Now < end)
        {
            if (!FileLockUtil.IsLocked(filePath))
            {
                return true;
            }

            Thread.Sleep(interval);
        }

        return false;
    }
}
C#

使い方の例は次の通りです。

string path = @"C:\data\input.csv";

bool unlocked = FileLockWaiter.WaitUntilUnlocked(
    path,
    timeout: TimeSpan.FromSeconds(10),
    interval: TimeSpan.FromMilliseconds(500));

if (!unlocked)
{
    Console.WriteLine("10秒待ってもファイルが空かなかったため、処理を諦めます。");
}
else
{
    Console.WriteLine("ファイルが空いたので、処理を開始します。");
}
C#

ここでの重要ポイントは、「待ち時間と間隔を引数で渡せるようにしている」ことです。
業務要件によって、「最大 30 秒まで待つ」「1 秒ごとにチェックする」など、柔軟に変えられます。


「ロック判定」の限界と注意点

判定と実際の操作の間に「時間差」がある

これが一番大事な注意点です。

今この瞬間に「ロックされていない」と判定できても、
その直後に別プロセスがファイルを開いてロックする可能性があります。

つまり、「ロックされていないと判定できた=この後の操作が必ず成功する」ではありません。
あくまで「今の時点では空いているように見える」だけです。

実務では、「ロック判定に成功したからといって油断せず、その後のファイル操作でも例外処理を書く」というのが正しい姿勢です。

ウイルススキャンやバックアップソフトなど、見えない相手

ファイルをロックするのは、自分のアプリや Excel だけではありません。
ウイルス対策ソフト、バックアップソフト、インデックスサービスなど、
OS や常駐ソフトも裏でファイルを開くことがあります。

それらはタイミングによって動いたり止まったりするので、
「たまにだけロックされて失敗する」という、再現しにくい現象の原因になりがちです。

だからこそ、「ロック判定ユーティリティ+リトライ+最終的な例外処理」という三段構えで設計しておくと、現場でのトラブルに強くなります。


例外とエラー処理を意識した設計

どこまでを「ロック」とみなすかを決める

IOException だけをロックとみなすのか、UnauthorizedAccessException も含めるのか、
あるいは「ファイルが存在しない場合はどう扱うか」など、細かい設計はプロジェクトごとに決める必要があります。

例えば、「存在しないならロックされていないとみなす」のか、
「そもそも存在しないのは別問題として扱う」のかで、ユーティリティの戻り値や例外設計が変わります。

大事なのは、「このユーティリティはどういうときに true を返すのか」をチーム内で共有しておくことです。
名前だけ見ると「ロックされているかどうか」ですが、実際には「このプロセスから触れない状態かどうか」を表していることも多いです。

ログに「なぜ触れなかったか」を残す

業務システムでは、「ファイルに触れなかった理由」をログに残しておくと、後からの調査がとても楽になります。

例えば、IsLocked の中で例外を握りつぶすのではなく、
呼び出し側で例外をキャッチしてログに書く設計にすることもあります。

どこまでログを出すかは要件次第ですが、少なくとも「どのファイルに対して」「どの操作をしようとして」「どう失敗したか」は残しておくと安心です。


まとめ 実務で使える「ファイルロック判定」ユーティリティの考え方

ファイルロック判定は、「魔法の API で状態を聞く」のではなく、
「自分がやりたいモードで実際に開いてみて、開けるかどうかで判断する」という発想が基本です。

押さえておきたいポイントを整理すると、こうなります。

C# には「ロック状態を直接返す API はない」。判定は「開いてみる」ことで行う。
FileStreamFileAccessFileShare の組み合わせで、「何がしたいか」「どこまで共有を許すか」を表現する。
IOException(場合によっては UnauthorizedAccessException)をキャッチして、「今は触れない状態」とみなすユーティリティを作る。
リトライ付きの「空くまで待つ」ユーティリティを用意しておくと、業務バッチなどで安定性が上がる。
判定と実際の操作の間には時間差があるので、最終的なファイル操作でも必ず例外処理を書く。

ここまで押さえておけば、「たまにファイルが使用中で落ちる」「誰が掴んでいるのか分からない」といった現場あるあるに、かなり冷静に対処できるようになります。

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