Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 783|回复: 0

C# 之 FileSystemWatcher事件多次触发的解决方法

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-5-8 03:50:19 | 显示全部楼层 |阅读模式

    1、问题描述
       程序里需要监视某个目录下的文件变化情况: 一旦目录中出现新文件或者旧的文件被覆盖,程序需要读取文件内容并进行处理。于是使用了下面的代码:

    public void Initial()
     {
       System.IO.FileSystemWatcher fsw = new System.IO.FileSystemWatcher();            
       fsw.Filter = "*.*";
       fsw.NotifyFilter = NotifyFilters.FileName  | 
                          NotifyFilters.LastWrite | 
                          NotifyFilters.CreationTime;
    
       // Add event handlers.
       fsw.Created += new FileSystemEventHandler(fsw_Changed);
       fsw.Changed += new FileSystemEventHandler(fsw_Changed);
    
       // Begin watching.
       fsw.EnableRaisingEvents = true;
     }
    
     void fsw_Changed(object sender, FileSystemEventArgs e)
     {
        MessageBox.Show("Changed", e.Name);
     }

      如果发现当一个文件产生变化时,Change事件被反复触发了好几次。这样可能的结果是造成同一文件的重复处理。

    2、解决方案:
        通过一个计时器,在文件事件处理中让计时器延迟一段时间之后,再执行加载新的配置文件操作。这样可以避免对文件做一次操作触发了多个更改事件,而多次加载配置文件。

      研究了log4net的代码 - XmlConfigurator.cs,然后参照log4net对代码作了如下改动:
      基本思想是使用定时器,在事件触发时开始启动定时器,并记下文件名。当定时器到时,才真正对文件进行处理。
      (1)、定义变量

    private int TimeoutMillis = 2000; //定时器触发间隔
    System.IO.FileSystemWatcher fsw = new System.IO.FileSystemWatcher();
    System.Threading.Timer m_timer = null;
    List<String> files = new List<string>(); //记录待处理文件的队列

      (2)、初始化FileSystemWatcher和定时器

           fsw.Filter = "*.*";
           fsw.NotifyFilter = NotifyFilters.FileName  | 
                              NotifyFilters.LastWrite | 
                              NotifyFilters.CreationTime;
    
           // Add event handlers.
          fsw.Created += new FileSystemEventHandler(fsw_Changed);
          fsw.Changed += new FileSystemEventHandler(fsw_Changed);
    
          // Begin watching.
          fsw.EnableRaisingEvents = true;
    
          // Create the timer that will be used to deliver events. Set as disabled
          if (m_timer == null)
          {
             //设置定时器的回调函数。此时定时器未启动
             m_timer = new System.Threading.Timer(new TimerCallback(OnWatchedFileChange), 
                                          null, Timeout.Infinite, Timeout.Infinite);
          }

      (3)、文件监视事件触发代码:修改定时器,记录文件名待以后处理

            void fsw_Changed(object sender, FileSystemEventArgs e)
            {
                Mutex mutex = new Mutex(false, "FSW");
                mutex.WaitOne();
                if (!files.Contains(e.Name))
                {
                    files.Add(e.Name);
                }
                mutex.ReleaseMutex();
    
                //重新设置定时器的触发间隔,并且仅仅触发一次
                m_timer.Change(TimeoutMillis, Timeout.Infinite);
            }

      (4)、定时器事件触发代码:进行文件的实际处理

            private void OnWatchedFileChange(object state)
            {
                List<String> backup = new List<string>();
    
                Mutex mutex = new Mutex(false, "FSW");
                mutex.WaitOne();
                backup.AddRange(files);
                files.Clear();
                mutex.ReleaseMutex();
           
                foreach (string file in backup)
                {
                    MessageBox.Show("File Change", file + " changed");
                }     
            }

     

      将上面的代码整理一下,封装成一个类,使用上更加便利一些:

        public class WatcherTimer
        {
            private int TimeoutMillis = 2000;
    
            System.IO.FileSystemWatcher fsw = new System.IO.FileSystemWatcher();
            System.Threading.Timer m_timer = null;
            List<String> files = new List<string>();
            FileSystemEventHandler fswHandler = null;
    
            public WatcherTimer(FileSystemEventHandler watchHandler)
            {
                m_timer = new System.Threading.Timer(new TimerCallback(OnTimer), 
                             null, Timeout.Infinite, Timeout.Infinite);
                fswHandler = watchHandler;
            }
    
            public WatcherTimer(FileSystemEventHandler watchHandler, int timerInterval)
            {
                m_timer = new System.Threading.Timer(new TimerCallback(OnTimer), 
                            null, Timeout.Infinite, Timeout.Infinite);
                TimeoutMillis = timerInterval;
                fswHandler = watchHandler;
            }
    
            public void OnFileChanged(object sender, FileSystemEventArgs e)
            {
                Mutex mutex = new Mutex(false, "FSW");
                mutex.WaitOne();
                if (!files.Contains(e.Name))
                {
                    files.Add(e.Name);
                }
                mutex.ReleaseMutex();
                m_timer.Change(TimeoutMillis, Timeout.Infinite);
            }
    
            private void OnTimer(object state)
            {
                List<String> backup = new List<string>();
                Mutex mutex = new Mutex(false, "FSW");
                mutex.WaitOne();
                backup.AddRange(files);
                files.Clear();
                mutex.ReleaseMutex();
    
                foreach (string file in backup)
                {
                    fswHandler(this, new FileSystemEventArgs(
                           WatcherChangeTypes.Changed, string.Empty, file));
                }
            }
    }

     

      在主调程序使用非常简单,只需要如下2步:
      1、生成用于文件监视的定时器对象

    watcher = new WatcherTimer(fsw_Changed, TimeoutMillis);

      其中fsw_Changed是你自己的文件监视事件代码,将它传递给定时器对象的目的是用于定时到时的时候定时器对象可以调用你自己真正用于处理文件的代码。例如:

    void fsw_Changed(object sender, FileSystemEventArgs e)
    {
       //Read file.
       //Remove file from folder after reading
          
    }

      2、将FileSystemWatcher的Create/Change/Rename/Delete等事件句柄关联到定时器的事件上

    fsw.Created += new FileSystemEventHandler(watcher.OnFileChanged);
    fsw.Changed += new FileSystemEventHandler(watcher.OnFileChanged);
    fsw.Renamed += new RenamedEventHandler(watcher.OnFileChanged);
    fsw.Deleted += new FileSystemEventHandler(watcher.OnFileChanged);

       这一步的目的是当有任何文件监视事件发生时,都能通知到定时器,定时器可以从最后一次发生的事件开始计时,在该计时未到时之前的任何事件都只会重新使计时器计时,而不会真正触发文件监视事件。

      要注意的是,采用了以上的代码后,你真正用于处理文件监视事件的代码被调用的时候只有其中的e.Name是有值的。考虑到被监视的文件目录应该已经知道了,所以e.FullPath被赋值为string.Empty并不是不能接受的。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-23 01:14 , Processed in 0.060239 second(s), 30 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表