【问题标题】:FileSystemWatcher skips some eventsFileSystemWatcher 跳过一些事件
【发布时间】:2015-10-09 23:38:04
【问题描述】:

如果您搜索FileSystemWatcher 问题,您会发现很多关于FileSystemWatcher 跳过某些事件(不触发所有事件)的文章。基本上,如果您更改了监视文件夹中的大量文件,其中一些将不会被FileSystemWatcher 处理。

为什么会这样,我怎样才能避免错过事件?

【问题讨论】:

  • 它不会跳过事件。不为 Error 事件编写事件处理程序是您的错误。没有其他方法可以发现您的事件处理程序太慢和/或您需要增加 InternalBufferSize。此外,在事件处理程序中使用 try/catch 来忽略告诉您无法打开文件的 IOException 也无能为力。
  • 当然你需要一个错误事件处理程序。我的代码只演示了文件更改检测和文件处理在两个线程中的分离。此外,我没有编写代码来检测文件是否已被创建它的进程释放。正如我在代码中评论的那样,该代码应该在 ProcessFile 函数中。

标签: c# filesystemwatcher


【解决方案1】:

原因

FileSystemWatcher 正在监视某个文件夹中发生的变化。当文件更改(例如创建文件)时,FileSystemWatcher 会引发相应的事件。事件处理程序可能会解压缩文件,读取其内容以决定如何进一步处理它,将其记录写入数据库日志表并将文件移动到另一个文件夹。文件的处理可能需要一些时间。

在此期间,可能会在监视文件夹中创建另一个文件。由于FileSystemWatcher 的事件处理程序正在处理第一个文件,它无法处理第二个文件的创建事件。因此,FileSystemWatcher 遗漏了第二个文件。

解决方案

由于文件处理可能需要一些时间,并且 FileSystemWatcher 可能无法检测到其他文件的创建,因此文件处理应与文件更改检测分开,并且文件更改检测应该如此短,以至于它不会错过单个文件更改。文件处理可以分为两个线程:一个用于文件更改检测,另一个用于文件处理。当文件被更改并被FileSystemWatcher 检测到时,适当的事件处理程序应该只读取其路径,将其转发到文件处理线程并关闭自身,以便FileSystemWatcher 可以检测到另一个文件更改并使用相同的事件处理程序。处理线程可能需要处理文件所需的时间。队列用于将文件路径从事件处理程序线程转发到处理线程。

这是典型的生产者-消费者问题。有关生产者-消费者队列的更多信息,请参阅here

代码

using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;

namespace FileSystemWatcherExample {
    class Program {
        static void Main(string[] args) {
            // If a directory and filter are not specified, exit program
            if (args.Length !=2) {
                // Display the proper way to call the program
                Console.WriteLine("Usage: Watcher.exe \"directory\" \"filter\"");
                return;
            }

            FileProcessor fileProcessor = new FileProcessor();

            // Create a new FileSystemWatcher
            FileSystemWatcher fileSystemWatcher1 = new FileSystemWatcher();

            // Set FileSystemWatcher's properties
            fileSystemWatcher1.Path = args[0];
            fileSystemWatcher1.Filter = args[1];
            fileSystemWatcher1.IncludeSubdirectories = false;

            // Add event handlers
            fileSystemWatcher1.Created += new System.IO.FileSystemEventHandler(this.fileSystemWatcher1_Created);

            // Start to watch
            fileSystemWatcher1.EnableRaisingEvents = true;

            // Wait for the user to quit the program
            Console.WriteLine("Press \'q\' to quit the program.");
            while(Console.Read()!='q');

            // Turn off FileSystemWatcher
            if (fileSystemWatcher1 != null) {
                fileSystemWatcher1.EnableRaisingEvents = false;
                fileSystemWatcher1.Dispose();
                fileSystemWatcher1 = null;
            }

            // Dispose fileProcessor
            if (fileProcessor != null)
                fileProcessor.Dispose();
        }

        // Define the event handler
        private void fileSystemWatcher1_Created(object sender, FileSystemEventArgs e) {
            // If file is created...
            if (e.ChangeType == WatcherChangeTypes.Created) {
                // ...enqueue it's file name so it can be processed...
                fileProcessor.EnqueueFileName(e.FullPath);
            }
            // ...and immediately finish event handler
        }
    }


    // File processor class
    class FileProcessor : IDisposable {
        // Create an AutoResetEvent EventWaitHandle
        private EventWaitHandle eventWaitHandle = new AutoResetEvent(false);
        private Thread worker;
        private readonly object locker = new object();
        private Queue<string> fileNamesQueue = new Queue<string>();

        public FileProcessor() {
            // Create worker thread
            worker = new Thread(Work);
            // Start worker thread
            worker.Start();
        }

        public void EnqueueFileName(string FileName) {
            // Enqueue the file name
            // This statement is secured by lock to prevent other thread to mess with queue while enqueuing file name
            lock (locker) fileNamesQueue.Enqueue(FileName);
            // Signal worker that file name is enqueued and that it can be processed
            eventWaitHandle.Set();
        }

        private void Work() {
            while (true) {
                string fileName = null;

                // Dequeue the file name
                lock (locker)
                    if (fileNamesQueue.Count > 0) {
                        fileName = fileNamesQueue.Dequeue();
                        // If file name is null then stop worker thread
                        if (fileName == null) return;
                    }

                if (fileName != null) {
                    // Process file
                    ProcessFile(fileName);
                } else {
                    // No more file names - wait for a signal
                    eventWaitHandle.WaitOne();
                }
            }
        }

        private ProcessFile(string FileName) {
            // Maybe it has to wait for file to stop being used by process that created it before it can continue
            // Unzip file
            // Read its content
            // Log file data to database
            // Move file to archive folder
        }


        #region IDisposable Members

        public void Dispose() {
            // Signal the FileProcessor to exit
            EnqueueFileName(null);
            // Wait for the FileProcessor's thread to finish
            worker.Join();
            // Release any OS resources
            eventWaitHandle.Close();
        }

        #endregion
    }
}

【讨论】:

  • 有趣。以下是有关丢失更改的记录:msdn.microsoft.com/en-us/library/…。 “Windows 操作系统会通知你的组件在 FileSystemWatcher 创建的缓冲区中的文件更改。如果短时间内有很多更改,缓冲区可能会溢出。这会导致组件丢失目录中的更改,它会只提供一揽子通知。”
  • 我怀疑对于大多数用例,这段代码应该防止内部缓冲区溢出。只要事件处理程序处理事件的速度比输入事件的速度快,内部缓冲区就永远不会溢出。
  • 埃里克 J. 是对的。此解决方案不适用于在短时间内进行许多更改以致缓冲区溢出的情况。这是针对两个文件的更改之间的时间比处理第一个文件的时间短的情况。在处理第一个文件期间,会更改第二个文件,因此事件处理程序无法处理它。如果FSW长时间观察很多变化,你肯定会遇到这个问题。
  • 为什么有些东西会丢失而不排队,这对我来说没有任何意义。 V8 也不会在忙时简单地丢弃事件?
  • 它在缓冲区中排队,但是缓冲区很小,并且在事件处理程序运行时短时间内有数百个事件可能会溢出。事件处理程序阻止引发事件的对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 2012-05-12
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多