【问题标题】:Using FileSystemWatcher with multiple files对多个文件使用 FileSystemWatcher
【发布时间】:2011-08-04 15:11:33
【问题描述】:

我想使用 FileSystemWatcher 来监视目录及其子目录中移动的文件。然后我想在所有文件都被移动后触发一些代码。但我不知道怎么做。我的代码会在每次移动文件时触发,如果用户一次移动多个文件,我只希望它为所有文件触发一次。所以基本上我想创建一个列表,一旦完成所有文件的移动,我想对该列表做一些事情......

代码如下:

class Monitor
{
    private List<string> _filePaths;  
    public void CreateWatcher(string path)
    {
        FileSystemWatcher watcher = new FileSystemWatcher();

        watcher.Filter = "*.*";

        watcher.Created += new
        FileSystemEventHandler(watcher_FileCreated);

        watcher.Path = path;
        watcher.IncludeSubdirectories = true;

        watcher.EnableRaisingEvents = true;
    }

    void watcher_FileCreated(object sender, FileSystemEventArgs e)
    {
        _filePaths.Add(e.FullPath);
        Console.WriteLine("Files have been created or moved!");
    }

}

更新:尝试使用 Chris 的代码,但它不起作用(请参阅我在 Chris 的回答中的评论):

class Monitor
    {
        private List<string> _filePaths;
        private Timer _notificationTimer;
        private FileSystemWatcher _fsw;
        public Monitor(string path)
        {
            _notificationTimer = new Timer();
            _notificationTimer.Elapsed += notificationTimer_Elapsed;
            // CooldownSeconds is the number of seconds the Timer is 'extended' each time a file is added.
            // I found it convenient to put this value in an app config file.
            int CooldownSeconds = 1;
            _notificationTimer.Interval = CooldownSeconds * 1000;

            _fsw = new FileSystemWatcher();
            _fsw.Path = path;
            _fsw.IncludeSubdirectories = true;
            _fsw.EnableRaisingEvents = true;

            // Set up the particulars of your FileSystemWatcher.
            _fsw.Created += fsw_Created;
        }

        private void notificationTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //
            // Do what you want to do with your List of files.
            //
            Console.Write("Done");
            // Stop the timer and wait for the next batch of files.            
            _notificationTimer.Stop();
            // Clear your file List.
            _filePaths = new List<string>();
        }


        // Fires when a file is created.
        private void fsw_Created(object sender, FileSystemEventArgs e)
        {
            // Add to our List of files.
            _filePaths.Add(e.Name);

            // 'Reset' timer.
            _notificationTimer.Stop();
            _notificationTimer.Start();
        }


    }

更新 2:

根据安德斯的回答试过这个:

public class FileListEventArgs : EventArgs
{
    public List<string> FileList { get; set; }
}

public class Monitor
{
    private List<string> filePaths;
    private ReaderWriterLockSlim rwlock;
    private Timer processTimer;
    public event EventHandler FileListCreated;


    public void OnFileListCreated(FileListEventArgs e)
    {
        if (FileListCreated != null)
            FileListCreated(this, e);
    }

    public Monitor(string path)
    {
        filePaths = new List<string>();

        rwlock = new ReaderWriterLockSlim();

        FileSystemWatcher watcher = new FileSystemWatcher();
        watcher.Filter = "*.*";
        watcher.Created += watcher_FileCreated;

        watcher.Path = path;
        watcher.IncludeSubdirectories = true;
        watcher.EnableRaisingEvents = true;
    }

    private void ProcessQueue()
    {
        List<string> list = new List<string>();
        try
        {
            Console.WriteLine("Processing queue, " + filePaths.Count + " files created:");
            rwlock.EnterReadLock();

        }
        finally
        {
            if (processTimer != null)
            {
                processTimer.Stop();
                processTimer.Dispose();
                processTimer = null;
                OnFileListCreated(new FileListEventArgs { FileList = filePaths });
                filePaths.Clear();
            }
            rwlock.ExitReadLock();
        }
    }

    void watcher_FileCreated(object sender, FileSystemEventArgs e)
    {
        try
        {
            rwlock.EnterWriteLock();
            filePaths.Add(e.FullPath);

            if (processTimer == null)
            {
                // First file, start timer.
                processTimer = new Timer(2000);
                processTimer.Elapsed += (o, ee) => ProcessQueue();
                processTimer.Start();
            }
            else
            {
                // Subsequent file, reset timer. 
                processTimer.Stop();
                processTimer.Start();
            }

        }
        finally
        {
            rwlock.ExitWriteLock();
        }
    }

我不得不将事件触发器移到 finally 语句中,这很有效。我不知道是否有什么理由我不想这样做?

【问题讨论】:

  • 您是否会从正在删除/添加文件的目录以及已移动文件的目录中获取事件?如果是这样,也许您可​​以忽略(或过滤)文件事件而只收听目录事件?编辑:如果添加了多个文件,您可能还会从目录中获得多个添加事件,对吗? ://
  • 我不太清楚你在这里问什么。您是否要在代码开始时填充列表,然后等到所有内容都被移动?还是您会逐渐填充列表?
  • @Chris:好吧,假设用户选择了许多文件并将它们移动到另一个子目录。在这种情况下,该事件将为每个文件触发(这将触发删除和创建事件,我正在使用创建事件来检测移动的文件)。因此,即使用户在一次操作中移动的每个文件都会触发事件,但我只想在所有文件都被移动后处理该事件。
  • @Anders:嗯,我不确定我是否完全理解你的问题,我对 FileSystemWatcher 很陌生,但是我认为你说的是​​正确的,应该有多个两者都触发,所以我不知道如何解决它......
  • 啊,好的。看起来杰的答案是要走的路。无论如何,我总是发现文件系统观察器有点不稳定!

标签: c# filesystemwatcher


【解决方案1】:

正如 Jay 所说:计时器可能是“分组”事件的唯一方法。锁可能有点矫枉过正,但我​​不喜欢在多线程情况下改变集合的想法(我认为来自 fswatcher 的事件是在池中的线​​程上调用的)。

  public class Monitor : IDisposable
  {
     private List<string> filePaths;
     private ReaderWriterLockSlim rwlock;
     private Timer processTimer;
     private string watchedPath;
     private FileSystemWatcher watcher;

     public Monitor(string watchedPath)
     {
        filePaths = new List<string>();

        rwlock = new ReaderWriterLockSlim();

        this.watchedPath = watchedPath;
        InitFileSystemWatcher();
     }

     private void InitFileSystemWatcher()
     {
        watcher = new FileSystemWatcher();
        watcher.Filter = "*.*";
        watcher.Created += Watcher_FileCreated;
        watcher.Error += Watcher_Error;
        watcher.Path = watchedPath;
        watcher.IncludeSubdirectories = true;
        watcher.EnableRaisingEvents = true;
     }

     private void Watcher_Error(object sender, ErrorEventArgs e)
     {
        // Watcher crashed. Re-init.
        InitFileSystemWatcher();
     }

     private void Watcher_FileCreated(object sender, FileSystemEventArgs e)
     {
        try
        {
           rwlock.EnterWriteLock();
           filePaths.Add(e.FullPath);

           if (processTimer == null)
           {
              // First file, start timer.
              processTimer = new Timer(2000);
              processTimer.Elapsed += ProcessQueue;
              processTimer.Start();
           }
           else
           {
              // Subsequent file, reset timer.
              processTimer.Stop();
              processTimer.Start();
           }

        }
        finally
        {
           rwlock.ExitWriteLock();
        }
     }

     private void ProcessQueue(object sender, ElapsedEventArgs args)
     {
        try
        {
           Console.WriteLine("Processing queue, " + filePaths.Count + " files created:");
           rwlock.EnterReadLock();
           foreach (string filePath in filePaths)
           {
              Console.WriteLine(filePath);
           }
           filePaths.Clear();
        }
        finally
        {
           if (processTimer != null)
           {
              processTimer.Stop();
              processTimer.Dispose();
              processTimer = null;
           }
           rwlock.ExitReadLock();
        }
     }

     protected virtual void Dispose(bool disposing)
     {
        if (disposing)
        {
           if (rwlock != null)
           {
              rwlock.Dispose();
              rwlock = null;
           }
           if (watcher != null)
           {
              watcher.EnableRaisingEvents = false;
              watcher.Dispose();
              watcher = null;
           }
        }
     }

     public void Dispose()
     {
        Dispose(true);
        GC.SuppressFinalize(this);
     }

  }     

记得在 fswatcher 上设置缓冲区大小,并在 fswatcher 出现错误时实现“复活”(即将错误事件绑定到重新创建 watcher 的方法)。

编辑:注意,本例中的计时器是 System.Timers.Timer,而不是 System.Threading.Timer

编辑:现在包含观察者的错误处理,处置逻辑。

【讨论】:

  • 好的,谢谢,这似乎几乎可以工作,但我不确定所有读锁和那些东西是怎么回事。它确实第一次使用文件路径列表中列出的所有文件到达 ProcessQueue。但随后它开始进入一些奇怪的循环,在代码中来回跳跃。怎么了?同样,它第一次到达那里时似乎确实有正确的信息,但随后它开始来回走动......不明白为什么......
  • 我更新了代码示例(错过了开始和停止),并对其进行了测试。现在似乎工作正常。锁就在那里,因此在队列处理期间没有文件添加到队列中。在上一批处理过程中发生的 fswatcher 事件只是等待并添加到下一批。
  • 谢谢,现在看来可以正常工作了!如果你不介意的话,只是几个问题:首先,我如何处理在 try 语句中重新创建观察者?在哪里?这里发生了什么: processTimer.Elapsed += (o, ee) => ProcessQueue();谢谢!
  • 我将令人困惑的 (o,ee) 更改为普通(等效)委托。我添加了失败后重新初始化观察者的代码,以及一些正确处置观察者的代码。
  • 好的,太好了。还有一件事:当我在列表完成后尝试引发一个事件以便将其返回到 winform 时,似乎 ProcessQueue 被调用了多次(每个文件一个?),而如果我没有在那里触发的事件(只是你的例子的foreach)然后它似乎不会被多次调用......这是我用foreach替换的内容: var fileListEventArgs = new FileListEventArgs {FileList = filePaths}; OnFileListCreated(fileListEventArgs);知道为什么吗?
【解决方案2】:

我必须做同样的事情。在 Monitor 类中使用 System.Timers.Timer 并对其 Elapsed 事件进行编码以处理您的文件列表并清除列表。当通过 FSW 事件将第一项添加到文件列表中时,启动计时器。当后续项目添加到列表中时,通过停止并重新启动计时器来“重置”计时器。

类似这样的:

class Monitor
{
    FileSystemWatcher _fsw;
    Timer _notificationTimer;
    List<string> _filePaths = new List<string>();

    public Monitor() {
        _notificationTimer = new Timer();
        _notificationTimer.Elapsed += notificationTimer_Elapsed;
        // CooldownSeconds is the number of seconds the Timer is 'extended' each time a file is added.
        // I found it convenient to put this value in an app config file.
        _notificationTimer.Interval = CooldownSeconds * 1000;

        _fsw = new FileSystemWatcher();
        // Set up the particulars of your FileSystemWatcher.
        _fsw.Created += fsw_Created;
    }

    private void notificationTimer_Elapsed(object sender, ElapsedEventArgs e) {
        //
        // Do what you want to do with your List of files.
        //

        // Stop the timer and wait for the next batch of files.            
        _notificationTimer.Stop();
        // Clear your file List.
        _filePaths = new List<string>();
    }


    // Fires when a file is created.
    private void fsw_Created(object sender, FileSystemEventArgs e) {
        // Add to our List of files.
        _filePaths.Add(e.Name);

        // 'Reset' timer.
        _notificationTimer.Stop();
        _notificationTimer.Start();
    }
}

【讨论】:

  • 谢谢杰,但我不确定。我可能会误解你,但也许我没有充分解释情况。我不知道如何调用此代码。因为发生的情况是,应用程序只会启动一次,然后它将监视目录中发生的所有事情。因此,唯一可以使任何事情发生的事情是如果 Created 事件被触发。我无法启动应用程序并手动调用启动计时器的方法。这是否意味着代码只能在启动应用程序后的前几秒钟内工作?还是我误会了你?
  • @Anders 定时器默认停止,并在 FSW Created 事件触发时启动。当 Timer Elapsed 事件触发时,Timer 会自行停止。我要补充一点,我的代码在 Windows 服务中持续运行。当服务启动时,应用程序启动并一直运行,直到服务停止。
  • 好吧,我一定是做错了什么,因为我试过了,稍作修改以与我的其余代码一起使用,但它不起作用。如果我移动一个文件,则会触发代码。但只有一次。下次我移动文件时,代码不会触发。当我尝试移动几个时,它永远不会到达已逝事件。查看更新!
【解决方案3】:

Rx - 油门 - 让这项工作变得简单。这是一个可测试、可重复使用的解决方案。

public class Files
{
     public static FileSystemWatcher WatchForChanges(string path, string filter, Action triggeredAction)
            {
                var monitor = new FileSystemWatcher(path, filter);

                //monitor.NotifyFilter = NotifyFilters.FileName;
                monitor.Changed += (o, e) => triggeredAction.Invoke();
                monitor.Created += (o, e) => triggeredAction.Invoke();
                monitor.Renamed += (o, e) => triggeredAction.Invoke();
                monitor.EnableRaisingEvents = true;

                return monitor;
            }
}

让我们将事件合并到一个序列中

  public IObservable<Unit> OnUpdate(string path, string pattern)
        {
            return Observable.Create<Unit>(o =>
            {
                var watcher = Files.WatchForChanges(path, pattern, () => o.OnNext(new Unit()));

                return watcher;
            });
        }

最后是用法

OnUpdate(path, "*.*").Throttle(Timespan.FromSeconds(10)).Subscribe(this, _ => DoWork())

您显然可以根据自己的需要调整油门速度。

【讨论】:

    【解决方案4】:

    另外,将缓冲区大小设置为大于默认值以避免缓冲区溢出。当超过 25 个文件被放入源目录(在我的测试中)时,就会发生这种情况。如果删除了 200 个文件,则仅对少数文件调用事件处理程序,而不是全部。

    _watcher.InternalBufferSize = 65536; //最大缓冲区大小

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-13
      • 2021-08-14
      相关资源
      最近更新 更多