将多个文件添加到目录时,FileSystemWatcher会出现文件访问错误

时间:2009-03-31 00:09:16

标签: c# exception-handling filesystemwatcher file-access

当多个文件放入监视目录时,我遇到了FileSystemWatcher的问题。我想在文件放入目录后立即解析它。通常,第一个文件解析正常,但向目录添加第二个文件会导致访问问题。偶尔,第一个文件甚至不会解析。只有一个应用程序正在运行并正在查看此目录。最终,此进程将在多台计算机上运行,​​并且它们将监视共享目录,但只有一台服务器可以解析每个文件,因为数据已导入数据库且没有主键。

这是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());
  }

移动第二个文件时,它正在捕捉

  

System.IO.IOException:进程无法访问文件'C:\ Temp \ TestFile.txt',因为它正由另一个进程使用。

如果它在多台计算机上运行,​​我希望看到这个错误,但它现在只在一台服务器上运行。不应该有另一个使用此文件的进程 - 我创建了它们并在应用程序运行时将它们复制到目录中。

这是设置FileSystemWatcher的正确方法吗?如何查看此文件的锁定?为什么不解析这两个文件 - 我是否必须关闭FileStream?我想保留FileShare.None选项,因为我只想要一个服务器来解析文件 - 获取文件的服务器首先解析它。

9 个答案:

答案 0 :(得分:52)

此方法的典型问题是在触发事件时仍在复制文件。显然,您将获得异常,因为文件在复制期间被锁定。大文件特别容易出现异常。

作为一种解决方法,您可以先复制该文件,然后重命名该文件并收听重命名事件。

或者另一种选择是使用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只会侦听触发器文件。

答案 1 :(得分:9)

我上面留下了评论,但我还没有足够的分数。

这个问题的最高评价答案有一段代码如下:

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.ReadFileShare.None FileShare.ReadWrite

答案 2 :(得分:4)

当您在OnChanged方法中打开文件时,您指定FileShare.None,根据the documentation,将导致任何其他尝试打开文件失败而您打开它。由于您(和您的观察者)正在阅读,请尝试使用FileShare.Read

答案 3 :(得分:2)

简单的解决方案是在收到通知后处理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);
}

答案 4 :(得分:2)

对于每个单独的文件创建,FileSystemWatcher都会触发watcher.Created事件两次 启动文件复制时为1ce,文件复制完成时为第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;
}

答案 5 :(得分:1)

我觉得你想要的一个很好的例子是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);
    }
}

答案 6 :(得分:1)

我有类似的问题。它只是因为FileSystemWatcher。我刚刚用过 Thread.sleep代码();

现在工作正常。 当文件进入目录时,它会调用onCreated两次。一旦文件被复制,第二次复制完成。为此,我使用了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);
        }
    }

答案 7 :(得分:0)

我在DFS中遇到了同样的问题。通过向每个文件添加两个空行来实现我的解决方案。然后我的代码等待文件中的两个空行。然后我确定从文件中读取整个数据。

答案 8 :(得分:0)

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);
        }
    }
}