Download filewatchermt.zip
In business world, file watchers play an important role in day to day operations. Many processes involve watching for particular files and then take appropriate actions. The implementations of these file watchers however are often not robust enough and sometimes result in file truncations or concurrency issues. In this article, I will show you how to construct a generic file watcher pattern where addresses these issues.
The easiest way to go about the file watcher process is to have a job run at a predefined interval. This method is also the most widely used in many companies. For example, suppose we want to watch whether a new file has come into our inbound folder, we could invoke a process once a minute to see if the file appears in the predefined location and if so we would take appropriate action.
There are two problems associated with this common approach however. First, what is the appropriate interval to check for file changes? Setting the interval too short might overburden the server the watcher process is running on but setting the interval too long might not respond to file changes in a timely fashion. Second, how do we know the file in the designated folder is in its final state? Suppose the file we are watching is delivered via ftp and the file is rather large, it could take sometime before the file is actually delivered. What happens when the file watcher process runs and sees the file there, should it grab the file even though it still might be incomplete? In most instances, people try to schedule a job around the time when the file to be watched might be changing and accept the risk of delivering incomplete file once a while.
We can eliminate both problems by reexamining the design of the file watcher system however. To address the first issue, we could use the FileSystemWatcher object provided in .Net framework to passively watch for the files and only take action when the file has been created or changed. To address the second issue, we could spawn a thread for each file that has changed or created and watch for its size to ensure that we only take actions after the size has stabilized.
The following diagram summarizes this approach:
Generic File Watcher
Now, let’s take a look at the code.
The FileStateObject encapsulates the state of a particular file that is being watched. Whenever a new file is detected, a FileStateObject will be created on a new thread. This thread then watches till the file size stabilizes before handling off to ProcessFile() where the custom actions take place.
public class FileStateObject
{
private string _fullPath = string.Empty;
private long _fileSize = 0;
private Hashtable _ht = null;
public FileStateObject(string fullPath, Hashtable ht)
{
_fullPath = fullPath;
_ht = ht;
}
private void ProcessFile()
{
//Code to process the file.
}
public void CheckFileStatus()
{
FileInfo fInfo = new FileInfo(_fullPath);
while (_fileSize != fInfo.Length)
{
_fileSize = fInfo.Length;
Thread.Sleep(2500);
fInfo = new FileInfo(_fullPath);
}
lock (this)
{
if (_ht.Contains(_fullPath))
{
_ht.Remove(_fullPath);
ProcessFile();
}
}
}
}
The FileWatcher class monitors the file changed events. Whenever a file changed event is fired, OnFileSystemChanged method is invoked. A hash table is used to determine whether we have taken care of the file from which the event is originated.
public class FileWatcher
{
private Hashtable _ht = new Hashtable();
private FileSystemWatcher _watcher = new FileSystemWatcher();
public void AddFileWatcher()
{
_watcher.Path = @"c:\temp";
_watcher.Filter = "*.txt";
_watcher.Created += new FileSystemEventHandler(OnFileSystemChanged);
_watcher.Changed += new FileSystemEventHandler(OnFileSystemChanged);
_watcher.EnableRaisingEvents = true;
}
public void RemoveFileWatcher()
{
_watcher.EnableRaisingEvents = false;
_watcher.Created -= new FileSystemEventHandler(OnFileSystemChanged);
_watcher.Changed -= new FileSystemEventHandler(OnFileSystemChanged);
}
private void OnFileSystemChanged(object sender, FileSystemEventArgs e)
{
lock (this)
{
if (!(_ht.Contains(e.FullPath)))
{
FileInfo fInfo = new FileInfo(e.FullPath);
_ht.Add(e.FullPath, e);
FileStateObject fStatus = new FileStateObject(e.FullPath, _ht);
Thread t = new Thread(new ThreadStart(fStatus.CheckFileStatus));
t.Start();
}
}
}
}
And finally, here is our test program. In real world though, the file watcher should be part of a long-running windows service.
class Program
{
static void
{
FileWatcher fw = new FileWatcher();
fw.AddFileWatcher();
Console.ReadKey();
fw.RemoveFileWatcher();
}
}