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

时间:2023-01-27 13:41:25

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选项,因为我只想要一个服务器来解析文件 - 获取文件的服务器首先解析它。

9 个解决方案

#1


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#代码看起来像这样(在生产系统中,您可能希望具有最大重试次数或超时而不是一段时间(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只会侦听触发器文件。

#2


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.ReadWrite setting 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.Open should be either FileShare.Read or FileShare.None, and NOT FileShare.ReadWrite.

File.Open调用中的共享设置应该是FileShare.Read或FileShare.None,而不是FileShare.ReadWrite。

#3


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.Read instead.

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

#4


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

#5


FileSystemWatcher fires watcher.Created event two times for 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为每个文件创建1ce触发watcher.Created事件两次,文件复制完成后第二次触发。您所要做的就是第二次忽略第一个事件和处理事件。

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

#6


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

#7


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

#8


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

#9


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

#1


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#代码看起来像这样(在生产系统中,您可能希望具有最大重试次数或超时而不是一段时间(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只会侦听触发器文件。

#2


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.ReadWrite setting 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.Open should be either FileShare.Read or FileShare.None, and NOT FileShare.ReadWrite.

File.Open调用中的共享设置应该是FileShare.Read或FileShare.None,而不是FileShare.ReadWrite。

#3


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.Read instead.

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

#4


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

#5


FileSystemWatcher fires watcher.Created event two times for 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为每个文件创建1ce触发watcher.Created事件两次,文件复制完成后第二次触发。您所要做的就是第二次忽略第一个事件和处理事件。

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

#6


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

#7


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

#8


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

#9


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