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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/699538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 13:57:28  来源:igfitidea点击:

File access error with FileSystemWatcher when multiple files are added to a directory

c#exception-handlingfilesystemwatcherfile-access

提问by Tai Squared

I am running into an issue with a FileSystemWatcher when multiple files are placed into the watched directory. I want to parse the file as soon as it is placed in the directory. Typically, the first file parses fine, but adding a second file to the directory causes an access issue. Occasionally, the first file doesn't even parse. There is only one application running and watching this directory. Eventually, this process will be running on multiple machines and they will be watching a shared directory but only one server can parse each file as the data is imported into a database and there are no primary keys.

当多个文件放入监视目录时,我遇到了 FileSystemWatcher 的问题。我想在将文件放入目录后立即对其进行解析。通常,第一个文件解析得很好,但是将第二个文件添加到目录会导致访问问题。有时,第一个文件甚至不解析。只有一个应用程序正在运行并监视此目录。最终,这个过程将在多台机器上运行,他们将观察一个共享目录,但只有一个服务器可以解析每个文件,因为数据被导入到数据库中,并且没有主键。

Here is the FileSystemWatcher code:

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

Then the method that parses the file:

然后解析文件的方法:

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

When moving the second file, it is catching

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

System.IO.IOException: The process cannot access the file 'C:\Temp\TestFile.txt' because it is being used by another process.

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

I would expect to see this error if it was running on multiple machines, but it is only running on one server for now. There shouldn't be another process using this file - I have them created and copy them into the directory when the application is running.

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

Is this the proper way to set up the FileSystemWatcher? How can I see what has the lock on this file? Why doesn't it parse both files - do I have to close the FileStream? I want to keep the FileShare.None option because I only want one server to parse the file - the server that gets to the file first parses it.

这是设置 FileSystemWatcher 的正确方法吗?我怎样才能看到这个文件上有什么锁?为什么它不解析这两个文件 - 我是否必须关闭 FileStream?我想保留 FileShare.None 选项,因为我只希望一台服务器解析文件 - 访问文件的服务器首先解析它。

采纳答案by Dirk Vollmar

A typical problem of this approach is that the file is still being copied while the event is triggered. Obviously, you will get an exception because the file is locked during copying. An exception is especially likely on large files.

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

As a workaround you could first copy the file and then rename it and listen to the renaming event.

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

Or another option would be to have a while loop checking whether the file can be opened with write access. If it can you will know that copying has been completed. C# code could look like this (in a production system you might want to have a maximum number of retries or timeout instead of a while(true)):

或者另一种选择是使用 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);
    }
}

Yet another approach would be to place a small trigger file in the folder after copying is completed. Your FileSystemWatcher would listen to the trigger file only.

另一种方法是在复制完成后在文件夹中放置一个小的触发器文件。您的 FileSystemWatcher 只会监听触发文件。

回答by dariol

I had the same problem within DFS. My resolution was achived by adding two empty lines to each file. Then my code waits for two empty lines in file. Then I have certainty to read whole data from file.

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

回答by Mike Powell

When you open the file in your OnChanged method, you're specifying FileShare.None, which according to the documentation, will cause any other attempts to open the file to fail while you've got it open. Since all you (and your watcher) are doing is reading, try using FileShare.Readinstead.

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

回答by Aravind Kathiroju

Simple solution would be to dispose the filesystemwatcher once you recieve the notification. before copying the file, make the current thread wait till it recieves the filesystemwatcher disposed event. then you can continue copying the changed file without access problems. I had same requirement and i did it exactly like what i mentioned. it worked.

简单的解决方案是在收到通知后处理 filesystemwatcher。在复制文件之前,让当前线程等待直到它收到 filesystemwatcher 处理事件。然后您可以继续复制更改的文件而不会出现访问问题。我有同样的要求,我完全按照我提到的去做。有效。

Example Code:

示例代码:

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

回答by Druegor

I feel a good example of what you want is the ConfigureAndWatchHandler in log4net. They use a timer to fire the file handler event. I feel this ends up being a cleaner implementation of the while loop in 0xA3's post. For those of you that don't want to use dotPeek to examine the file I'll try to give you a code snippet here based on the OP code:

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

回答by G-Mac

I'd have left a comment above, but I don't have enough points yet.

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

The top-rated answer to this question has a block of code that look like this:

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

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

The problem with using FileShare.ReadWritesetting is that it is requesting access to the file basically saying "I want to read/write to this file, but others can also read/write to it." This approach failed in our situation. The process that was receiving the remote transfer did not put a lock on the file, but it was actively writing to it. Our downstream code (SharpZipLib) was failing with the "file in use" exception because it was trying to open the file with a FileShare.Read("I want the file for reading, and only let other processes read as well"). Because the process that had the file open was already writing to it, this request failed.

使用FileShare.ReadWrite设置的问题在于它请求访问文件基本上是说“我想读/写这个文件,但其他人也可以读/写它。” 这种方法在我们的情况下失败了。接收远程传输的进程没有锁定文件,而是主动写入文件。我们的下游代码 (SharpZipLib) 因“文件正在使用”异常而失败,因为它试图用一个FileShare.Read(“我想要读取文件,并且只让其他进程读取”)打开文件。因为打开文件的进程已经在写入文件,所以这个请求失败了。

However, the code in the response above is too relaxed. By using FileShare.ReadWrite, it was succeeding in obtaining access to the file (because it was asking for a Share restriction that could be honored), but the downstream call continued to fail.

但是,上面响应中的代码太宽松了。通过使用FileShare.ReadWrite,它成功地获得了对文件的访问权(因为它要求可以遵守的共享限制),但下游调用继续失败。

The share setting in the call to File.Openshould be either FileShare.Reador FileShare.None, and NOTFileShare.ReadWrite.

调用中的共享设置File.Open应该是FileShare.ReadorFileShare.NoneNOTFileShare.ReadWrite

回答by AjitChahal

FileSystemWatcher fires watcher.Created event two timesfor every single file creation 1ce when file copy is started and 2nd time when file copy is finished. All you have to do is ignore 1st event and process event the second time.

FileSystemWatcher的触发事件watcher.Created两次为每一个文件创建时1CE文件拷贝开始,当文件复制完成第二次。您所要做的就是忽略第一个事件并第二次处理事件。

A simple example of event handler:

事件处理程序的一个简单示例:

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

回答by Andreas

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

回答by Renascent

I had similar problem. Its just because of FileSystemWatcher. I just used
Thread.Sleep();

我有类似的问题。这只是因为 FileSystemWatcher。我刚用过
Thread.Sleep();

And its working fine now. When file comes in directory it calls onCreated twice. so once when file being copied.and second time when copying completed. For that I used Thread.Sleep(); So it will wait before I call ReadFile();

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