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

2024-01-29 03:58

本文主要是介绍FileSystemWatcher事件多次触发的解决方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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、解决方案:
在Google上进行一番搜索后,得到了下面的一段信息: <<http://www.cnblogs.com/RicCC/archive/2006/12/16/filesystem-watcher.html>>
"...可以参考log4net的做法。通过一个计时器,在文件事件处理中让计时器延迟一段时间之后,再执行加载新的配置文件操作。这样可以避免对文件做一次操作触发了多个更改事件,而多次加载配置文件。"

研究了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(
thisnew 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并不是不能接受的。

这篇关于FileSystemWatcher事件多次触发的解决方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/655662

相关文章

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

Python安装Pandas库的两种方法

《Python安装Pandas库的两种方法》本文介绍了三种安装PythonPandas库的方法,通过cmd命令行安装并解决版本冲突,手动下载whl文件安装,更换国内镜像源加速下载,最后建议用pipli... 目录方法一:cmd命令行执行pip install pandas方法二:找到pandas下载库,然后

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原