magnify
formats

FileSystemWatcher with Timer for Single Notification of File Changes

Published on April 13, 2012
.NET has a handy type called System.IO.FileSystemWatcher which you can point at a directory and it triggers events based on file events such as a file being created, deleted, renamed, or changed.  The problem is that the Changed event gets triggered many times with a simple file save when usually you only want to be notified once when the change is done.

Too Many Change Notices

For example, just saving ‘hello world’ to hello.txt using Notepad triggers the Created event and then three Changed events.  Most often code watching for a file change only wants to be notified once, after the file is finished being written to.  This can be particularly troublesome if for example you have code that needs to process a file that is written to by a slow operation, such as an FTP transfer or network file copy, possibly triggering the Changed event 100s of times before the file transfer is complete. What we want here is a more transactional or atomic single notice.

Use a Timer to Dampen Event Triggers

A simple solution is to implement dampening using a Timer.  Essentially ignoring the little spikes using a time delay that gets reset on change events close together.  We start a Timer at the first indication of a file creation or change and reset on each subsequent change within a short time period.  Then we’ll listen to the Timer’s Elapsed event for the real final notification that changes have subsided.  The trick then is to make the Timer’s delay something longer than the longest time between normal Changed events, but small enough to quickly notify that changes have stopped.  My measurements have shown that it’s about 2 to 50 ms between Changed events, so a timer interval of 200ms tends to work well.

Example Code

Download Code: FileWatcherExample.7z Without the timer you wouldn’t get the “File done:” notice. All this shows is saving “hello world” to a hello.txt file. It gets a created then three changed event triggers. With the timer we get notified when it’s done. console
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Timers;

namespace FileWatcherExample
{
  class Program
  {
    // Keep track of the timers for various files
    private static Dictionary<string, Timer> timers = new Dictionary<string, Timer>();

    static void Main(string[] args)
    {
      // Create the watcher and subscribe to events
      FileSystemWatcher fsw = new FileSystemWatcher(@"c:\temp\watchme");
      fsw.Created += new FileSystemEventHandler(fsw_Trigger);
      fsw.Changed += new FileSystemEventHandler(fsw_Trigger);
      fsw.Deleted += new FileSystemEventHandler(fsw_Trigger);
      fsw.Renamed += new RenamedEventHandler(fsw_Trigger);
      fsw.EnableRaisingEvents = true;

      // Continue listening until [ENTER] is pressed
      Console.WriteLine("Monitoring...");
      Console.ReadLine();
    }

    static void fsw_Trigger(object sender, FileSystemEventArgs e)
    {
      // Let the user know what type of change was triggered
      Console.WriteLine("File {0}: {1}", e.ChangeType, e.Name);

      // If a file was created or changed, start a timer
      if (e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed)
      {
        Timer t;

        // Create a new timer if one isn't already assigned to this file
        if (timers.ContainsKey(e.FullPath)) t = timers[e.FullPath];
        else
        {
          // Adjust interval for your needs, 200ms is generally good
          timers.Add(e.FullPath, t = new Timer() { Interval = 200, AutoReset = false });
          t.Elapsed += new ElapsedEventHandler(fileTimer_Elapsed);
        }

        // Reset the timer
        t.Stop(); t.Start();
      }
    }
   
    static void fileTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
      // A timer has elapsed, find out which file it belonged to and inform user
      Timer t = sender as Timer;
      string file = timers.First(a => a.Value == t).Key;
      Console.WriteLine("File done: " + file);

      // Remove the timer so the runtime will dispose of it
      timers.Remove(file);
    }
  }
}

Closing Remarks

It’s also good to be aware of the various Timers in .NET as a different timer than I’m using above may be more appropriate. For example if you’re working with a Windows Forms or WPF app you may want to use the UI framework Timer to make it easier to update UI on the right UI thread.
 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
2 Comments  comments 

2 Responses

  1. Anderson Imes

    This was a fun little exercise to do with the Reactive Extensions. I used a library called “Rxx” that provides a handy extension method for turning the events into observables, but you can do this yourself, as well. Anyway, interesting problem.
    https://gist.github.com/3012923

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>