web-dev-qa-db-ja.com

ファイルが使用中かどうかを確認する方法はありますか?

私は1つの画像ファイルに繰り返しアクセスする必要があるC#でプログラムを書いています。ほとんどの場合はうまくいきますが、私のコンピュータが高速で動いている場合、ファイルシステムに保存される前にファイルにアクセスしようとし、エラーが発生します:"他のプロセスが使用中のファイル"

私はこれを回避する方法を見つけたいと思いますが、私のGooglingはすべて例外処理を使用して小切手を作成するだけでした。これは私の宗教に反するので、誰かがそれをするより良い方法があるかどうか私は思っていましたか?

776
Dawsy

この解決策に関する注記を更新しました FileAccess.ReadWriteによるチェックは読み取り専用ファイルでは失敗するため、解決策はFileAccess.Readによるチェックに変更されました。 FileAccess.Readでチェックしようとするとファイルに書き込みまたは読み取りロックがあると失敗するため、この解決策は機能しますが、ファイルに書き込みまたは読み取りロックがないと、この解決法は機能しません。 FileShare.ReadまたはFileShare.Writeアクセスで開かれた(読み書き用)。

オリジナル: 私は過去数年間このコードを使ってきましたが、問題はありませんでした。

例外を使用することについての躊躇を理解してください、しかしあなたはいつもそれらを避けることはできません:

protected virtual bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}
499
ChrisW

これに関するスレッドの競合状態に悩まされる可能性があります。これは、セキュリティ上の脆弱性として使用されていることの文書化された例があります。ファイルが利用可能であることを確認してから試して使用すると、その時点でスローされる可能性があります。悪意のあるユーザーがこれを使用してコードを強制的に悪用する可能性があります。

あなたの最善の策は、ファイルハンドルを取得しようとするtry catch/finallyです。

try
{
   using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
   {
        // File/Stream manipulating code here
   }
} catch {
  //check here why it failed and ask user to retry if the file is in use.
}
529
Spence

ファイルがロックされているかどうかをチェックするためにこれを使います:

using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;

private static bool IsFileLocked(Exception exception)
{
    int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
    return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}

internal static bool CanReadFile(string filePath)
{
    //Try-Catch so we dont crash the program and can check the exception
    try {
        //The "using" is important because FileStream implements IDisposable and
        //"using" will avoid a heap exhaustion situation when too many handles  
        //are left undisposed.
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
            if (fileStream != null) fileStream.Close();  //This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the "using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
        }
    }
    catch (IOException ex) {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex)) {
            // do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
            return false;
        }
    }
    finally
    { }
    return true;
}
}

パフォーマンス上の理由から、同じ操作でファイルの内容を読むことをお勧めします。ここではいくつかの例を示します。

public static byte[] ReadFileBytes(string filePath)
{
    byte[] buffer = null;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
                sum += count;  // sum is a buffer offset for next reading

            fileStream.Close(); //This is not needed, just me being paranoid and explicitly releasing resources ASAP
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }
    return buffer;
}

public static string ReadFileTextWithEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            //Depending on the encoding you wish to use - I'll leave that up to you
            fileContents = System.Text.Encoding.Default.GetString(buffer);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    { }     
    return fileContents;
}

public static string ReadFileTextNoEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) 
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            char[] chars = new char[buffer.Length / sizeof(char) + 1];
            System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
            fileContents = new string(chars);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }

    return fileContents;
}

自分で試してみてください。

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
82
Jeremy Thompson

おそらく、 FileSystemWatcher を使用してChangedイベントを監視できます。

私はこれを自分で使ったことはありませんが、一撃の価値があるかもしれません。この場合、ファイルシステムウォッチャーが少し重いことがわかった場合は、try/catch/sleepループに進みます。

6
Karl Johan

意図したとおりに例外を使用してください。ファイルが使用中であることを受け入れて、アクションが完了するまで繰り返してください。行動する前に状態をチェックするサイクルを無駄にしないため、これも最も効率的です。

例えば以下の関数を使ってください。

TimeoutFileAction(() => { System.IO.File.etc...; return null; } );

2秒後にタイムアウトする再利用可能な方法

private T TimeoutFileAction<T>(Func<T> func)
{
    var started = DateTime.UtcNow;
    while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
    {
        try
        {
            return func();                    
        }
        catch (System.IO.IOException exception)
        {
            //ignore, or log somewhere if you want to
        }
    }
    return default(T);
}
5
kernowcode

利用可能になるとすぐにストリームを提供するタスクを返すことができます。それは単純化された解決策ですが、それは良い出発点です。スレッドセーフです。

private async Task<Stream> GetStreamAsync()
{
    try
    {
        return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
    }
    catch (IOException)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        return await GetStreamAsync();
    }
}

あなたはいつものようにこのストリームを使うことができます:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
4
Ivan Branets
static bool FileInUse(string path)
    {
        try
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
            {
                fs.CanWrite
            }
            return false;
        }
        catch (IOException ex)
        {
            return true;
        }
    }

string filePath = "C:\\Documents And Settings\\yourfilename";
bool isFileInUse;

isFileInUse = FileInUse(filePath);

// Then you can do some checking
if (isFileInUse)
   Console.WriteLine("File is in use");
else
   Console.WriteLine("File is not in use");

お役に立てれば!

4
Julian

私が知っている唯一の方法はそれほど速くないWin32排他ロックAPIを使用することですが、例が存在します。

ほとんどの人は、これに対する簡単な解決策として、単純に/ catch/sleepループを試すことです。

4
Luke Schafer

上記の一般的な回答では、ファイルがFileShare.Readモードで書き込み用に開かれている場合、またはファイルに読み取り専用属性が含まれている場合、コードが機能しないという問題があります。この修正された解決策は、2つの点に留意することで最も確実に機能します(承認された解決策にも当てはまります)。

  1. 書き込み共有モードで開かれたファイルに対しては機能しません。
  2. これはスレッドの問題を考慮に入れていないので、ロックするかスレッドの問題を個別に処理する必要があります。

上記を念頭に置いて、これはファイルが書き込み用にロックされている読み取りを防ぐためにロックされているのどちらであるかをチェックします。

public static bool FileLocked(string FileName)
{
    FileStream fs = null;

    try
    {
        // NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
        fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
    }
    catch (UnauthorizedAccessException) // https://msdn.Microsoft.com/en-us/library/y973b725(v=vs.110).aspx
    {
        // This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
        try
        {
            fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
        }
        catch (Exception)
        {
            return true; // This file has been locked, we can't even open it to read
        }
    }
    catch (Exception)
    {
        return true; // This file has been locked
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return false;
}
3
rboy

3ライナーを使用するだけでなく、参照用:full blown情報が必要な場合は、Microsoft Dev Centerに小さなプロジェクトがあります:

https://code.msdn.Microsoft.com/windowsapps/How-to-know-the-process-704839f4

はじめに:

.NET Framework 4.0で開発されたC#サンプルコードは、ファイルをロックしているプロセスを特定するのに役立ちます。 rstrtmgr.dllに含まれるRmStartSession関数は再起動マネージャーセッションの作成に使用され、戻り結果に応じてWin32Exceptionオブジェクトの新しいインスタンスが作成されます。 RmRegisterRescources関数を使用してリソースをRestart Managerセッションに登録した後、RmGetList関数は、RM_PROCESS_INFO配列を列挙することにより、アプリケーションが特定のファイルを使用しているものをチェックするために呼び出されます。

「Restart Manager Session」に接続することで機能します。

再起動マネージャは、セッションに登録されているリソースのリストを使用して、シャットダウンして再起動する必要があるアプリケーションとサービスを決定します。 リソースは、実行中のアプリケーションを記述するファイル名、サービスの短縮名、またはRM_UNIQUE_PROCESS構造で識別できます。

それはあなたの特定のニーズのために少し過剰に設計されている可能性があります...しかしそれが(youが望むものであれば、先に進み、vsプロジェクトを取得します。

3
Bernhard

あなたは複数のアプリからファイルにアクセスするために私のライブラリを使うことができます。

あなたはnugetからそれをインストールすることができます:Install-Package Xabe.FileLock

あなたがそれについての詳細な情報が必要な場合は https://github.com/tomaszzmuda/Xabe.FileLock を確認してください。

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}

fileLock.Acquireメソッドは、このオブジェクト専用のファイルをロックできる場合にのみtrueを返します。ただし、ファイルをアップロードするアプリもファイルロックでやらなければならないアプリ。オブジェクトにアクセスできない場合metodはfalseを返します。

2
Tomasz Żmuda

これが私の知る限りでは、受け入れられた答えと同じことをするが、より少ないコードで行うコードです。

    public static bool IsFileLocked(string file)
    {
        try
        {
            using (var stream = File.OpenRead(file))
                return false;
        }
        catch (IOException)
        {
            return true;
        }        
    }

しかし、次のようにしたほうが堅牢だと思います。

    public static void TryToDoWithFileStream(string file, Action<FileStream> action, 
        int count, int msecTimeOut)
    {
        FileStream stream = null;
        for (var i = 0; i < count; ++i)
        {
            try
            {
                stream = File.OpenRead(file);
                break;
            }
            catch (IOException)
            {
                Thread.Sleep(msecTimeOut);
            }
        }
        action(stream);
    }
2
cdiggins

私の経験では、あなたは通常これをし、それからあなたのファイルを空想のために「保護」してから「保護された」ファイルを使用したいと思います。このように使いたいファイルが1つだけの場合は、Jeremy Thompsonによる回答で説明されているトリックを使用できます。しかし、たくさんのファイルに対してこれをやろうとすると(例えばインストーラーを書いているときなど)、かなり痛い状態になっています。

これを解決することができる非常に洗練された方法はそれがそこにあるファイルの1つが使われているならあなたのファイルシステムがあなたがフォルダ名を変えることをあなたに許さないという事実を使うことです。フォルダを同じファイルシステムに保存すると、魅力的に動作します。

これが悪用される可能性がある明白な方法を知っておく必要があることに注意してください。結局、ファイルはロックされません。また、Move操作が失敗する可能性がある他の理由があることにも注意してください。明らかに適切なエラー処理(MSDN)がここで助けになることができます。

var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name here
var someFolder = Path.Combine(originalFolder, "..", Guid.NewGuid().ToString("N"));

try
{
    Directory.Move(originalFolder, someFolder);

    // Use files
}
catch // TODO: proper exception handling
{
    // Inform user, take action
}
finally
{
    Directory.Move(someFolder, originalFolder);
}

個々のファイルについては、Jeremy Thompsonが投稿したロックの提案に固執します。

1
atlaste

これがWTFの反射を引き起こすかどうかに興味があります。コンソールアプリからPDFドキュメントを作成して起動するプロセスがあります。ただし、以前に生成されたファイルを閉じずに同じファイルを生成してユーザーがプロセスを複数回実行すると、アプリケーションが例外をスローして終了するという脆弱性を扱っていました。ファイル名は販売見積もり番号に基づいているため、これはかなり頻繁に発生しました。

そのような不正な方法で失敗するのではなく、私はファイルの自動増分バージョン管理に頼ることにしました。

private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
    try
    {
        var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
        var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
        using (var writer = new FileStream(filePath, FileMode.Create))
        {
            writer.Write(data, 0, data.Length);
        }
        return filePath;
    }
    catch (IOException)
    {
        return WriteFileToDisk(data, fileName, ++version);
    }
}

おそらく、私が正しいIOExceptionを捕まえられるように、catchブロックにもう少し注意を払うことができます。これらのファイルはとにかく一時的なものであることを意図しているので、私はおそらく起動時にもアプリのストレージをクリアします。

これは、ファイルが使用中かどうかを確認するというOPの問題の範囲を超えていることを認識していますが、実際にここに到着したときに解決しようとしていた問題です。

0
Vinney Kelly

このようなものが役立ちますか?

var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
    try
    {
        lock (new Object())
        {
            using (StreamWriter streamWriter = new StreamWriter(filepath.txt"), true))
            {
                streamWriter.WriteLine("text");
            }
        }

        fileWasWrittenSuccessfully = true;
    }
    catch (Exception)
    {

    }
}
0
Tadej

かつて、オンラインバックアップアーカイブにPDFをアップロードする必要がありました。ただし、ユーザーが別のプログラム(PDFリーダーなど)でファイルを開いている場合、バックアップは失敗します。急いで、私はこのスレッドのいくつかの上位の回答を試みましたが、それらを機能させることができませんでした。私にとっては、PDFファイルを独自のディレクトリに移動しようとしていました。ファイルが別のプログラムで開かれている場合、これは失敗し、移動が成功した場合、別のディレクトリに移動された場合のように復元操作は必要ないことがわかりました。他の特定のユースケースに役立つかもしれない場合に備えて、基本的なソリューションを投稿したいと思います。

string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
    fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
    open_elsewhere = true;
}

if (open_elsewhere)
{
    //handle case
}