ファイルがファイルウォッチャーフォルダーにコピーされているときに、ファイルが完全にコピーされて使用できる状態になっているかどうかを確認するにはどうすればよいですか?ファイルのコピー中に複数のイベントが発生するためです。(ファイルは、File.Copyを使用して別のプログラムを介してコピーされます。)
8 に答える
私がこの問題に遭遇したとき、私が思いついた最善の解決策は、ファイルの排他的ロックを継続的に取得しようとすることでした。ファイルが書き込まれている間、ロックの試行は失敗します。これは基本的にこの回答の方法です。ファイルが書き込まれなくなると、ロックは成功します。
残念ながら、それを行う唯一の方法は、ファイルを開くときにtry / catchをラップすることです。これにより、私はうんざりします。try/catchを使用する必要があるのは常に苦痛です。しかし、それを回避する方法はないように思われるので、それを使用することになりました。
その答えのコードを変更するとうまくいくので、私は次のようなものを使用することになりました:
private void WaitForFile(FileInfo file)
{
FileStream stream = null;
bool FileReady = false;
while(!FileReady)
{
try
{
using(stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
FileReady = true;
}
}
catch (IOException)
{
//File isn't ready yet, so we need to keep on waiting until it is.
}
//We'll want to wait a bit between polls, if the file isn't ready.
if(!FileReady) Thread.Sleep(1000);
}
}
これは、ファイルアクセスを X 回まで再試行する方法ですSleep
。アクセスできない場合、アプリケーションは次に進みます。
private static bool GetIdleFile(string path)
{
var fileIdle = false;
const int MaximumAttemptsAllowed = 30;
var attemptsMade = 0;
while (!fileIdle && attemptsMade <= MaximumAttemptsAllowed)
{
try
{
using (File.Open(path, FileMode.Open, FileAccess.ReadWrite))
{
fileIdle = true;
}
}
catch
{
attemptsMade++;
Thread.Sleep(100);
}
}
return fileIdle;
}
次のように使用できます。
private void WatcherOnCreated(object sender, FileSystemEventArgs e)
{
if (GetIdleFile(e.FullPath))
{
// Do something like...
foreach (var line in File.ReadAllLines(e.FullPath))
{
// Do more...
}
}
}
ファイルの書き込み時にこの問題が発生しました。ファイルが完全に書き込まれて閉じられる前にイベントを受け取りました。
解決策は、一時的なファイル名を使用し、終了後にファイルの名前を変更することです。次に、ファイルの作成または変更イベントではなく、ファイルの名前変更イベントを監視します。
注: この問題は一般的なケースでは解決できません。ファイルの使用に関する事前の知識がなければ、他のプログラムがファイルの操作を終了したかどうかを知ることはできません。
あなたの特定のケースでは、 File.Copy がどのような操作で構成されているかを理解できるはずです。
ほとんどの場合、宛先ファイルは操作全体でロックされています。この場合、ファイルを開いて「共有モード違反」例外を処理するだけでよいはずです。
しばらく待つこともできます...-非常に信頼性の低いオプションですが、ファイルのサイズ範囲がわかっている場合は、コピーを終了させるのに妥当な遅延を設けることができる場合があります。
また、ある種のトランザクション システムを「発明」することもできます。つまり、「destination_file_name.COPYLOCK」のような別のファイルを作成します。ファイルをコピーするプログラムは、「destination_file_name」をコピーする前に作成し、後で削除します。
private Stream ReadWhenAvailable(FileInfo finfo, TimeSpan? ts = null) => Task.Run(() =>
{
ts = ts == null ? new TimeSpan(long.MaxValue) : ts;
var start = DateTime.Now;
while (DateTime.Now - start < ts)
{
Thread.Sleep(200);
try
{
return new FileStream(finfo.FullName, FileMode.Open);
}
catch { }
}
return null;
})
.Result;
...もちろん、ニーズに合わせてこの側面を変更できます。
この問題を 2 つの機能で解決しました。
MemoryCache
この質問に見られるパターンを実装してください: FileSystemWatcher がイベントを複数回発生させるための堅牢なソリューション- アクセスのタイムアウトを伴う try\catch ループを実装する
環境内の平均コピー時間を収集し、メモリ キャッシュ タイムアウトを少なくとも新しいファイルの最短ロック時間と同じ長さに設定する必要があります。これにより、処理ディレクティブの重複がなくなり、コピーが完了するまでの時間が確保されます。最初の試行でより良い成功が得られます。つまり、try\catch ループに費やされる時間が短縮されます。
以下は、try\catch ループの例です。
public static IEnumerable<string> GetFileLines(string theFile)
{
DateTime startTime = DateTime.Now;
TimeSpan timeOut = TimeSpan.FromSeconds(TimeoutSeconds);
TimeSpan timePassed;
do
{
try
{
return File.ReadLines(theFile);
}
catch (FileNotFoundException ex)
{
EventLog.WriteEntry(ProgramName, "File not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (PathTooLongException ex)
{
EventLog.WriteEntry(ProgramName, "Path too long: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (DirectoryNotFoundException ex)
{
EventLog.WriteEntry(ProgramName, "Directory not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (Exception ex)
{
// We swallow all other exceptions here so we can try again
EventLog.WriteEntry(ProgramName, ex.Message, EventLogEntryType.Warning, ex.HResult);
}
Task.Delay(777).Wait();
timePassed = DateTime.Now.Subtract(startTime);
}
while (timePassed < timeOut);
EventLog.WriteEntry(ProgramName, "Timeout after waiting " + timePassed.ToString() + " seconds to read " + theFile, EventLogEntryType.Warning, 258);
return null;
}
設定をどこTimeoutSeconds
にでも置くことができる設定はどこにありますか。これは、環境に合わせて調整できます。