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並不是不能接受的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM