web-dev-qa-db-ja.com

ディレクトリに複数のファイルを追加すると、FileSystemWatcherでファイルアクセスエラーが発生する

複数のファイルが監視対象のディレクトリに配置されると、FileSystemWatcherで問題が発生します。ファイルがディレクトリに配置されたらすぐに解析したい。通常、最初のファイルは正しく解析されますが、2番目のファイルをディレクトリに追加すると、アクセスの問題が発生します。場合によっては、最初のファイルが解析されないこともあります。このディレクトリを実行して監視しているアプリケーションは1つだけです。最終的に、このプロセスは複数のマシンで実行され、共有ディレクトリを監視しますが、データがデータベースにインポートされ、主キーがないため、各ファイルを解析できるサーバーは1つだけです。

FileSystemWatcherコードは次のとおりです。

public void Run() {
  FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp");
  watcher.NotifyFilter = NotifyFilters.FileName;
  watcher.Filter = "*.txt";

  watcher.Created += new FileSystemEventHandler(OnChanged);

  watcher.EnableRaisingEvents = true;
  System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}

次に、ファイルを解析するメソッド:

private void OnChanged(object source, FileSystemEventArgs e) {
  string line = null;

  try {
    using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) {
      using (StreamReader sr = new StreamReader(fs)) {
        while (sr.EndOfStream == false) {
          line = sr.ReadLine();
          //parse the line and insert into the database
        }
      }
    }
  }
  catch (IOException ioe) {
    Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString());
  }

2番目のファイルを移動するとき、それはキャッチしています

System.IO.IOException:プロセスはファイル 'C:\ Temp\TestFile.txt'にアクセスできません。別のプロセスによって使用されているためです。

複数のマシンで実行されている場合はこのエラーが表示されると思いますが、現時点では1つのサーバーでのみ実行されています。このファイルを使用する別のプロセスがあってはなりません。アプリケーションを実行しているときに作成してディレクトリにコピーします。

これはFileSystemWatcherを設定する適切な方法ですか?このファイルのロックを確認するにはどうすればよいですか?なぜ両方のファイルを解析しないのですか?FileStreamを閉じる必要がありますか? FileShare.Noneオプションを保持したいのは、ファイルを解析するサーバーを1つだけにしたいためです。ファイルにアクセスしたサーバーが最初にファイルを解析します。

38
Tai Squared

このアプローチの典型的な問題は、イベントがトリガーされている間、ファイルがまだコピーされていることです。当然ながら、コピー中にファイルがロックされるため、例外が発生します。例外は、特に大きなファイルで発生する可能性があります。

回避策として、最初にファイルをコピーしてから名前を変更し、名前変更イベントをリッスンすることができます。

または、ファイルを書き込みアクセスで開くことができるかどうかを確認するwhileループを使用することもできます。できればコピー完了です。 C#コードは次のようになります(本番システムでは、while(true)の代わりに最大回数の再試行またはタイムアウトが必要になる場合があります)。

/// <summary>
/// Waits until a file can be opened with write permission
/// </summary>
public static void WaitReady(string fileName)
{
    while (true)
    {
        try
        {
            using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                if (stream != null)
                {
                    System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
                    break;
                }
            }
        }
        catch (FileNotFoundException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        catch (IOException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        catch (UnauthorizedAccessException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        Thread.Sleep(500);
    }
}

さらに別のアプローチは、コピーが完了した後、小さなトリガーファイルをフォルダーに配置することです。 FileSystemWatcherはトリガーファイルのみをリッスンします。

51
Dirk Vollmar

上のコメントを残しておきましたが、まだ十分なポイントがありません。

この質問に対する最高評価の回答には、次のようなコードブロックがあります。

using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
    if (stream != null)
    {
        System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
        break;
    }
}

FileShare.ReadWrite設定を使用する際の問題は、基本的に「このファイルの読み取り/書き込みが必要ですが、他のユーザーも読み取り/書き込みができる」と言ってファイルへのアクセスを要求していることです。このアプローチは私たちの状況では失敗しました。リモート転送を受信して​​いたプロセスは、ファイルをロックしませんでしたが、アクティブにファイルに書き込んでいました。ダウンストリームコード(SharpZipLib)は、FileShare.Readでファイルを開こうとしたため、「使用中のファイル」例外で失敗しました(「ファイルを読み取り用に必要とし、他のプロセスにも読み取りを許可する」) 。ファイルを開いているプロセスはすでに書き込みを行っていたため、このリクエストは失敗しました。

ただし、上記の応答のコードは緩和されすぎています。 FileShare.ReadWriteを使用することで、ファイルへのアクセス権の取得に成功しました(守られる共有制限を要求していたため)が、ダウンストリームの呼び出しは失敗し続けました。

File.Openの呼び出しの共有設定は、FileShare.ReadまたはFileShare.Noneのいずれかである必要があります[〜#〜] not [〜#〜]FileShare.ReadWrite

10
G-Mac

OnChangedメソッドでファイルを開くときは、FileShare.Noneを指定します。これにより、 documentation に応じて、ファイルを取得している間に他のファイルを開こうとすると失敗します。開いた。あなた(とあなたのウォッチャー)がしていることはすべて読んでいるので、代わりにFileShare.Readを使用してみてください。

4
Mike Powell

FileSystemWatcherは、watcher.Createdイベントを発生させます2回すべてのファイル作成ごとに1回ファイルコピーが開始され、2回目はファイルコピーが終了します。あなたがしなければならないのは、最初のイベントを無視し、2回目にイベントを処理することです。

イベントハンドラーの簡単な例:

private bool _fileCreated = false;
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e)
{
    if (_fileCreated)
    {
        ReadFromFile();//just an example method call to access the new file
    }

    _fileCreated = !_fileCreated;
}
2
AjitChahal

簡単な解決策は、通知を受け取ったらfilesystemwatcherを破棄することです。ファイルをコピーする前に、現在のスレッドがfilesystemwatcher破棄イベントを受け取るまで待機させます。その後、アクセスの問題なしに変更されたファイルのコピーを続行できます。私は同じ要件を持っていて、私が言ったこととまったく同じようにそれをしました。動いた。

コード例:

public void TestWatcher()
{
    using (var fileWatcher = new FileSystemWatcher())
    {

        string path = @"C:\sv";
        string file = "pos.csv";

        fileWatcher.Path = path;
        fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
        fileWatcher.Filter = file;

        System.EventHandler onDisposed = (sender,args) =>
        {
           eve.Set();
        };

        FileSystemEventHandler onFile = (sender, fileChange) =>
        {
           fileWatcher.EnableRaisingEvents = false;
           Thread t = new Thread(new ParameterizedThreadStart(CopyFile));
           t.Start(fileChange.FullPath);
           if (fileWatcher != null)
           {
               fileWatcher.Dispose();
           }
           proceed = false;
        };

        fileWatcher.Changed += onFile;
        fileWatcher.Created += onFile;
        fileWatcher.Disposed+= onDisposed;
        fileWatcher.EnableRaisingEvents = true;

        while (proceed)
        {
            if (!proceed)
            {
                break;
            }
        }
    }
}

public void CopyFile(object sourcePath)
{
    eve.WaitOne();
    var destinationFilePath = @"C:\sv\Co";
    if (!string.IsNullOrEmpty(destinationFilePath))
    {
        if (!Directory.Exists(destinationFilePath))
        {
            Directory.CreateDirectory(destinationFilePath);
        }
        destinationFilePath = Path.Combine(destinationFilePath, "pos.csv");
    }           

    File.Copy((string)sourcePath, destinationFilePath);
}
2

あなたが望むものの良い例は、log4netのConfigureAndWatchHandlerです。タイマーを使用して、ファイルハンドラーイベントを発生させます。私はこれが0xA3の投稿のwhileループのよりクリーンな実装になると感じています。 dotPeekを使用してファイルを調べたくない方のために、OPコードに基づいて、ここでコードスニペットを提供します。

private System.Threading.Timer _timer;    

public void Run() {
  //setup filewatcher
  _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1);
}

private void OnFileChange(object state)
{
    try 
    {
    //handle files
    }
    catch (Exception ex) 
    {
        //log exception
        _timer.Change(500, -1);
    }
}
1
Druegor

同様の問題がありました。 FileSystemWatcherが原因です。使ったばかり
Thread.Sleep();

そして、現在は正常に機能しています。ファイルがディレクトリに入ると、onCreatedを2回呼び出します。ファイルがコピーされるときに1回、コピーが完了するときに2回目です。そのためにThread.Sleep();を使用しました。したがって、ReadFile()を呼び出す前に待機します。

private static void OnCreated(object source, FileSystemEventArgs e)
    {
        try
        {
            Thread.Sleep(5000);
            var data = new FileData();
            data.ReadFile(e.FullPath);                
        }
        catch (Exception ex)
        {
            WriteLogforError(ex.Message, String.Empty, filepath);
        }
    }
1
Renascent
public static BitmapSource LoadImageNoLock(string path)
{
    while (true)
    {
        try
        {
            var memStream = new MemoryStream(File.ReadAllBytes(path));
            var img = new BitmapImage();
            img.BeginInit();
            img.StreamSource = memStream;
            img.EndInit();
            return img;
            break;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}
0
Andreas

DFSでも同じ問題が発生しました。私の解決策は、各ファイルに2つの空の行を追加することで達成されました。次に、私のコードはファイル内の2つの空の行を待ちます。その後、ファイルからデータ全体を確実に読み取ることができます。

0
dariol