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

相关文章

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

使用jenv工具管理多个JDK版本的方法步骤

《使用jenv工具管理多个JDK版本的方法步骤》jenv是一个开源的Java环境管理工具,旨在帮助开发者在同一台机器上轻松管理和切换多个Java版本,:本文主要介绍使用jenv工具管理多个JD... 目录一、jenv到底是干啥的?二、jenv的核心功能(一)管理多个Java版本(二)支持插件扩展(三)环境隔

Java中Map.Entry()含义及方法使用代码

《Java中Map.Entry()含义及方法使用代码》:本文主要介绍Java中Map.Entry()含义及方法使用的相关资料,Map.Entry是Java中Map的静态内部接口,用于表示键值对,其... 目录前言 Map.Entry作用核心方法常见使用场景1. 遍历 Map 的所有键值对2. 直接修改 Ma

Mybatis Plus Join使用方法示例详解

《MybatisPlusJoin使用方法示例详解》:本文主要介绍MybatisPlusJoin使用方法示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录1、pom文件2、yaml配置文件3、分页插件4、示例代码:5、测试代码6、和PageHelper结合6

MySQL 设置AUTO_INCREMENT 无效的问题解决

《MySQL设置AUTO_INCREMENT无效的问题解决》本文主要介绍了MySQL设置AUTO_INCREMENT无效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录快速设置mysql的auto_increment参数一、修改 AUTO_INCREMENT 的值。

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

关于跨域无效的问题及解决(java后端方案)

《关于跨域无效的问题及解决(java后端方案)》:本文主要介绍关于跨域无效的问题及解决(java后端方案),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录通用后端跨域方法1、@CrossOrigin 注解2、springboot2.0 实现WebMvcConfig

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

解决JSONField、JsonProperty不生效的问题

《解决JSONField、JsonProperty不生效的问题》:本文主要介绍解决JSONField、JsonProperty不生效的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录jsONField、JsonProperty不生效javascript问题排查总结JSONField