【问题标题】:File access error with FileSystemWatcher when multiple files are added to a directory将多个文件添加到目录时,FileSystemWatcher 出现文件访问错误
【发布时间】:2010-10-16 12:27:32
【问题描述】:

当将多个文件放入监视目录时,我遇到了 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 选项,因为我只希望一台服务器解析文件 - 获取文件的服务器首先解析它。

【问题讨论】:

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


    【解决方案1】:

    我有类似的问题。只是因为 FileSystemWatcher。我刚用过
    线程.睡眠();

    它现在工作正常。 当文件进入目录时,它会调用两次 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);
            }
        }
    

    【讨论】:

      【解决方案2】:

      我觉得你想要的一个很好的例子是 log4net 中的 ConfigureAndWatchHandler。他们使用计时器来触发文件处理程序事件。我觉得这最终成为 0xA3 帖子中 while 循环的更清晰的实现。对于那些不想使用 dotPeek 检查文件的人,我将尝试在此处根据 OP 代码为您提供代码 sn-p:

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

      【讨论】:

        【解决方案3】:
        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);
                }
            }
        }
        

        【讨论】:

          【解决方案4】:

          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】:

            简单的解决方案是在收到通知后处理文件系统观察程序。在复制文件之前,让当前线程等待,直到它收到 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);
            }
            

            【讨论】:

            • 如果您正在处理多个文件并且您处置了文件系统观察程序,那么您将度过一段糟糕的时光。
            【解决方案6】:

            我会在上面留下评论,但我还没有足够的积分。

            这个问题的评分最高的答案有一段代码,如下所示:

            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

            【讨论】:

              【解决方案7】:

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

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                  【解决方案9】:

                  这种方法的一个典型问题是在触发事件时文件仍在被复制。显然,您会得到一个异常,因为文件在复制过程中被锁定。大文件尤其容易出现异常。

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

                  或者另一种选择是有一个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 只会监听触发文件。

                  【讨论】:

                  • 这些测试文件很小——只有几行短文本,所以在复制过程中不应该锁定太久。我将如何循环检查文件是否准备好写入?
                  • 谢谢!我很惭愧,我想不出一个 while 循环来解决这个问题——我需要读取另一个应用程序的输出文件的更新版本。每次写入我都会收到多个事件 - 所以我使用了一个每次重置的计时器,并在触发读取更新文件的代码之前等待 X 秒。但是如果应用在 X 秒内写入两次,我就会错过更新。
                  • +1 但是你真的应该有一个最长的等待时间,这样进程就不会永远被锁定
                  • 不需要 FileAccess.ReadWrite 或 FileShare.ReadWrite;我使用了 FileAccess.Read、FileShare.None 并且工作正常,即如果您可以获得排他锁来读取文件,那么您知道其他进程已完成创建文件并释放它。
                  • @joedotnot 你的解决方案对我不起作用。即使设置 FileAccess.Read 和 FileShare.None ,我仍然会遇到有关文件被另一个进程使用的问题。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多