web-dev-qa-db-ja.com

ファイルが完全に書き込まれるまで待つ

あるディレクトリにファイルが作成されたら(FileSystemWatcher_Created)、別のディレクトリにコピーします。しかし、大きな(> 10MB)ファイルを作成すると、ファイルのコピーが失敗します。ファイルの作成が完了していないときに、既にコピーを開始しているためです...
これにより、ファイルは別のプロセスで使用されているためコピーできませんが発生します。 ;(
助けが必要ですか?

class Program
{
    static void Main(string[] args)
    {
        string path = @"D:\levan\FolderListenerTest\ListenedFolder";
        FileSystemWatcher listener; 
        listener = new FileSystemWatcher(path);
        listener.Created += new FileSystemEventHandler(listener_Created);
        listener.EnableRaisingEvents = true;

        while (Console.ReadLine() != "exit") ;
    }

    public static void listener_Created(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine
                (
                    "File Created:\n"
                   + "ChangeType: " + e.ChangeType
                   + "\nName: " + e.Name
                   + "\nFullPath: " + e.FullPath
                );
        File.Copy(e.FullPath, @"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
        Console.Read();
    }
}
63
levi

直面している問題の回避策のみがあります。

コピーのプロセスを開始する前に、ファイルIDが処理中かどうかを確認してください。 False値を取得するまで、次の関数を呼び出すことができます。

最初の方法、 この回答から直接コピー

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

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, 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;
}

第2の方法:

const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
    //check that problem is not in destination file
    if (File.Exists(file) == true)
    {
        FileStream stream = null;
        try
        {
            stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        }
        catch (Exception ex2)
        {
            //_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
            int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
            if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
            {
                return true;
            }
        }
        finally
        {
            if (stream != null)
                stream.Close();
        }
    }
    return false;
}
40

FileSystemWatcherのドキュメントから:

OnCreatedイベントは、ファイルが作成されるとすぐに発生します。ファイルが監視ディレクトリにコピーまたは転送されている場合、OnCreatedイベントがすぐに発生し、その後に1つ以上のOnChangedイベントが続きます。

そのため、コピーが失敗した場合(例外をキャッチ)、それをまだ移動する必要があるファイルのリストに追加し、OnChangedイベント中にコピーを試行します。最終的には動作するはずです。

次のようなもの(不完全;特定の例外をキャッチ、変数を初期化など):

    public static void listener_Created(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine
                (
                    "File Created:\n"
                   + "ChangeType: " + e.ChangeType
                   + "\nName: " + e.Name
                   + "\nFullPath: " + e.FullPath
                );
        try {
            File.Copy(e.FullPath, @"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
        }
        catch {
            _waitingForClose.Add(e.FullPath);
        }
        Console.Read();
    }

    public static void listener_Changed(object sender, FileSystemEventArgs e)
    {
         if (_waitingForClose.Contains(e.FullPath))
         {
              try {
                  File.Copy(...);
                  _waitingForClose.Remove(e.FullPath);
              }
              catch {}
         }
   }
10
Steve Czetty

古いスレッドですが、他の人のために情報を追加します。

PDFファイルを書き込むプログラムで同様の問題が発生しました。レンダリングに30秒かかることがあります。これは、watcher_FileCreatedクラスがファイルをコピーする前に待機するのと同じ期間です。

ファイルはロックされていません。

この場合、PDFのサイズを確認し、2秒待ってから新しいサイズを比較しました。それらが等しくない場合、スレッドは30秒間スリープし、再試行します。

10
Johnny Grimes

あなたは実際に幸運です-ファイルを書き込むプログラムはそれをロックするので、あなたはそれを開くことができません。ロックされていなかった場合、問題があるとは思わずに、部分的なファイルをコピーしていたでしょう。

ファイルにアクセスできない場合、そのファイルはまだ使用中であると想定できます(さらに良いのは、排他モードで開いて、File.Copyの失敗から推測するのではなく、誰かが現在開いているかどうかを確認することです)。ファイルがロックされている場合は、別のときにコピーする必要があります。ロックされていない場合は、コピーできます(ここに競合状態の可能性がわずかにあります)。

その「別の時間」はいつですか? FileSystemWatcherがファイルごとに複数のイベントを送信するとき、私は記憶していません-チェックしてください。イベントを単に無視して別のイベントを待つだけで十分かもしれません。そうでない場合は、いつでも時間を設定して、5秒以内にファイルを再確認できます。

5
zmbq

さて、あなたはすでに自分で答えを与えています。ファイルの作成が完了するまで待つ必要があります。これを行う1つの方法は、ファイルがまだ使用されているかどうかを確認することです。この例はここにあります: ファイルが使用中かどうかを確認する方法はありますか?

状況に応じてこのコードを変更する必要があることに注意してください。 (擬似コード)のようなものが必要な場合があります。

public static void listener_Created()
{
   while CheckFileInUse()
      wait 1000 milliseconds

   CopyFile()
}

明らかに、所有者アプリケーションがロックを決して解除しない場合に備えて、無限のwhileから身を守る必要があります。また、サブスクライブできるFileSystemWatcherからの他のイベントをチェックアウトする価値があるかもしれません。この問題全体を回避するために使用できるイベントがあるかもしれません。

2
pyrocumulus

そのため、これらの質問やその他の類似の質問をすぐに見て、今日の午後、メリーゴースチェイスで、同期(およびファイル保存)メソッドとしてファイルを使用する2つの別個のプログラムの問題を解決しようとしました。ちょっと変わった状況ですが、「ファイルがロックされているかどうかを確認し、ロックされていない場合は開く」というアプローチの問題を明確に強調しました。

問題はこれです:ファイルはbecome確認してから実際にファイルを開くまでロックできます。散発性を追跡するのは本当に難しいファイルをコピーできません。別のプロセスで使用されているため探していない場合はエラーになります。

基本的な解決策は、catchブロック内でファイルを開こうとすることで、ロックされている場合は再試行できます。そのようにして、チェックとオープニングの間に経過時間はなく、OSはそれらを同時に行います。

ここのコードはFile.Copyを使用していますが、Fileクラスの静的メソッドであるFile.Open、File.ReadAllText、File.WriteAllTextなどと同様に機能します。

/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
    while (timeout > 0)
    {
        try
        {
            File.Copy(src, dst);

            //don't forget to either return from the function or break out fo the while loop
            break;
        }
        catch (IOException)
        {
            //you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
        }
        Thread.Sleep(100);

        //if its a very long wait this will acumulate very small errors. 
        //For most things it's probably fine, but if you need precision over a long time span, consider
        //   using some sort of timer or DateTime.Now as a better alternative
        timeout -= 100;
    }
}

並列性に関する別の小さなメモ:これは同期メソッドであり、待機中およびスレッドでの作業中にスレッドをブロックします。これが最も簡単なアプローチですが、ファイルが長時間ロックされたままになると、プログラムが応答しなくなる可能性があります。ここでは詳細を説明するのに大きすぎるトピックです(また、非同期の読み取り/書き込みを設定できる方法の数は一種の不合理です)が、ここではそれを並列化する方法の1つを説明します。

public class FileEx
{
    public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
    {
        while (timeout > 0)
        {
            try
            {
                File.Copy(src, dst);
                doWhenDone();
                break;
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
    }

    public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
    {
        while (timeout > 0)
        {
            try {
                return File.ReadAllText(filePath);
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
        return "";
    }

    public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
    {
        while (timeout > 0)
        {
            try
            {
                File.WriteAllText(filePath, contents);
                return;
            }
            catch (IOException) { }

            await Task.Delay(100);
            timeout -= 100;
        }
    }
}

そして、それがどのように使用される可能性があります:

public static void Main()
{
    test_FileEx();
    Console.WriteLine("Me First!");
}    

public static async void test_FileEx()
{
    await Task.Delay(1);

    //you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
    //As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
    //until the first 'await'. See the documentation for async: https://msdn.Microsoft.com/en-us/library/hh156513.aspx
    CopyWaitAsync("file1.txt", "file1.bat", 1000);

    //this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
    await CopyWaitAsync("file1.txt", "file1.readme", 1000);
    Console.WriteLine("file1.txt copied to file1.readme");

    //The following line doesn't cause a compiler error, but it doesn't make any sense either.
    ReadAllTextWaitAsync("file1.readme", 1000);

    //To get the return value of the function, you have to use this function with the await keyword
    string text = await ReadAllTextWaitAsync("file1.readme", 1000);
    Console.WriteLine("file1.readme says: " + text);
}

//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!
2
ashbygeek

ファイルがバイナリ(バイト単位)で書き込まれている場合、FileStream以上のソリューションを作成します。ファイルはすべてのバイトで準備が完了し、書き込みが行われるため、動作しません。ファイルの処理を開始する

long fileSize = 0;
currentFile = new FileInfo(path);

while (fileSize < currentFile.Length)//check size is stable or increased
{
  fileSize = currentFile.Length;//get current size
  System.Threading.Thread.Sleep(500);//wait a moment for processing copy
  currentFile.Refresh();//refresh length value
}

//Now file is ready for any process!
1
Mohsen.Sharify

次のコードを使用して、ファイルを排他的アクセスで開くことができる(つまり、別のアプリケーションで開かない)かどうかを確認できます。ファイルが閉じられていない場合は、しばらく待ってから、ファイルが閉じられるまでもう一度確認し、安全にコピーできます。

File.Copyが失敗するかどうかを確認する必要があります。これは、ファイルをチェックしてからコピーするまでの間に別のアプリケーションがファイルを開く可能性があるためです。

public static bool IsFileClosed(string filename)
{
    try
    {
        using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
        {
            return true;
        }
    }
    catch (IOException)
    {
        return false;
    }
}
0
Michael