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

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

时间: 2024-08-01 16:42:56

C# 之 FileSystemWatcher事件多次触发的解决方法的相关文章

百度地图JS API移动端,phonegap 自定义覆盖物Click事件无法执行 的 bug 解决方法

当你为自定义覆盖物添加了click事件后,你会发现在移动端是无论如何也触发不了的,这算是一个BUG. 最近我在为我的地图APP开发第二版,在为一个覆盖物添加行为事被困扰到了,自定义覆盖物的click事件竟然无法在iPhone端触发,以及百度地图JS API里面的开源库涉及到自定义覆盖物click事件的均无效. 我为这个BUG烦恼了很久,直到今天,在QQ群里网友(在此要先感谢这位网友mooring)告诉了我解决方法:iPhone上的自定义覆盖物事件默认会触发map的click事件,如果map定义了

ios微信浏览器click事件不起作用的解决方法

$(document).on( "click", ".weui_cell", function (event) {alert(); }); JS代码是这样的,html代码是动态追加进去的,click事件在苹果手机没作用,在安卓和pc端事件完全正常 经过一番搜索,解决方法也很奇葩,只需在html代码中添加onclick=""   就可以了,记录下来 ,让后面看到的人少走弯路 如:<div class="weui_cell"

js中hover事件时候的BUG以及解决方法

hover事件是我们在开发前段时候遇到的稀松平常的问题,但是有没有发现会出现有一个BUg,比如,你移动到一个元素上,让它执行一个方法,然后你快速的移入移出的时候,他会进行亮瞎你眼睛的频闪效果,而且跟得了老年痴呆一样会进行延时显示,24K钛合金也会被闪瞎的,鼠标重复在相应区域滑动的时候,动画会一直执行,直到和鼠标经过的次数相同位置.比如鼠标移进3次,移出3次,动画就会出现三次.这显然是极度影响用户体验的.然后我们公司大后端告诉我说把mouseevent和monseleave合并成一个hover事件

Windows7事件ID10,WMI错误的解决方法

近期电脑频繁死机,通过查看日志发现.事件ID10,WMI错误通过搜索找到解决方法,拷贝以下代码到文本文件里,另存为扩展名为"修复.vbs",并运行一路确定.代码: strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\" & strComputer & "

jquery事件重复绑定的快速解决方法

click等事件 解决:使用unbind("click")方法先解除绑定的事件再绑定新事件,即在给对象绑定事件之前先移除该对象上的原有事件 1 $("#test2").unbind('click').click(function() { 2 alert("click解除绑定执行" + k++ + "次"); 3 }); 转自:http://www.cnblogs.com/shiyou00/p/5531049.html

JQuery给元素绑定click事件多次执行的解决方法

原绑定方法: $(".subNavdiv").click(function () { ###### }); 这种方法只会在原click方法中继续添加新方法: 解决办法更改绑定方法为: $("#sdfsd").unbind("click").click(function(e){ ###### }); 在绑定新click方法前对元素所绑定的click方法解绑

移动端多点触摸touchend 事件没有正常触发

移动端触屏事件主要是touchstart,touchmove,touchend 执行顺序touchstart(开始) - touchmove(移动) - touchend(结束) for (var i = 0; i < dom.length; i++) { dom[i].addEventListener('touchstart', function (e) { ..... }, false); dom[i].addEventListener('touchend', function (e) {

onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法

1. onchange事件监听input值变化的使用方法: <input id="test"></input> $("input").change(function(){ alert("aaa"); } 2. 网页开发时,如果有input隐藏域,通过js改变隐藏域的值,无法触发change事件. 原因:onchange事件在内容改变(两次内容有可能还是相等的)且失去焦点时触发. 解决方法:在这种情况下,可以在改变隐藏域的值

label点击事件重复调用原因与解决办法

label>input结构,再给label绑定点击事件,会导致点击时执行2次点击事件的现象.比如下面的代码: <div class="radio"> <label> <input type="radio" ng-click="func($event)" id="ue" > 编辑器模式 </label> </div> <div class="rad