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.
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);
}
}
}
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);
}
}
}












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
nifty! thanks Anderson, look forward to trying Rxx