How to: Safely use FileSystemWatcher.Created events
On my current assignment, I was asked to build a simple windows service to monitor a directory on the server. Any file placed in that directory had to be moved to another location as soon as it arrived. The locations differed based on the file's contents.
Nothing special of course. I soon had a service running which used the following code to monitor that directory:
private FileSystemWatcher _watcher;
private void SetupWatcher()
{ _watcher = new FileSystemWatcher(@"C:\Inbox", "*.csv");
_watcher.Created += new FileSystemEventHandler(_watcher_Created);
}
private void _watcher_Created(object sender, FileSystemEventArgs e)
{ this.Process(e.FullPath);
}
The Process method would handle everything. It worked perfectly, except for the odd IOException telling me the file was in use by another process. The FileSystemWatcher object raises the Created event whenever a file is created. But it does so as soon as the first byte is written. So in fact, my application was telling me it couldn't process the file, because another process was still writing the file to the specified directory.
In one of the forums on TheScripts.com, I found a solution which I modified slightly to suit my needs. Here's what I did to solve the problem. I now have a method that will try to open the file exclusively. When the File.Open method fails, it will throw an IOException. The method is then able to tell me if the file is still being used:
/// <summary>
/// Check if the file upload has been completed.
/// </summary>
/// <param name="filename">The name of file to check.</param>
/// <returns>
/// Returns true if the specified file has completed uploading and
/// is ready for processing.
/// </returns>
private static bool FileUploadCompleted(string filename)
{ // If the file can be opened for exclusive access it means that the file
// is no longer locked by another process.
try
{ using (FileStream inputStream = File.Open(filename, FileMode.Open,
FileAccess.Read,
FileShare.None))
{ return true;
}
}
catch (IOException)
{ return false;
}
}
Using this method, the event handler for the Created Event is modified. A loop is introduced in which the application checks if the file is in use. If that is the case, the loop will wait for a short period of time and try again. To make sure the application doesn't retry indefinitely, a check is implemented to see if a predefined retry period has passed. If that is the case, an error is written to the eventlog and the loop is stopped:
private void _watcher_Created(object sender, FileSystemEventArgs e)
{ DateTime fileReceived = DateTime.Now;
while (true)
{ if (FileUploadCompleted(e.FullPath))
{ this.Process(e.FullPath);
break;
}
// Calculate the elapsed time and stop if the maximum retry
// period has been reached.
TimeSpan timeElapsed = DateTime.Now - fileReceived;
if (timeElapsed.TotalMinutes > MaximumRetryPeriod)
{ WriteToEventLog(string.Format(CultureInfo.CurrentCulture, "The file \"{0}\" could not be processed.", e.FullPath)); break;
}
System.Threading.Thread.Sleep(RetryDelay);
}
}
This method works really well. I hope it will be of some use to some of you.