This one's actually a bit of a doozie, unless the problem space has changed significantly since I last had to deal with it.
The easiest way is to simply try to open the file, catch the resulting IOException
, and if the file is locked, add it to a queue to be checked later. You can't just try to process every file that comes in because there are all kinds of cases where multiple events will be generated for the same file, so setting up a retry loop on every single received event can turn into a disaster, fast. You need to queue them up instead and check the queue at a regular interval.
Here is a basic class template that should help you out with this problem:
public class FileMonitor : IDisposable
{
private const int PollInterval = 5000;
private FileSystemWatcher watcher;
private HashSet<string> filesToProcess = new HashSet<string>();
private Timer fileTimer; // System.Threading.Timer
public FileMonitor(string path)
{
if (path == null)
throw new ArgumentNullException("path");
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Created += new FileSystemEventHandler(FileCreated);
watcher.EnableRaisingEvents = true;
fileTimer = new Timer(new TimerCallback(ProcessFilesTimer),
null, PollInterval, Timeout.Infinite);
}
public void Dispose()
{
fileTimer.Dispose();
watcher.Dispose();
}
private void FileCreated(object source, FileSystemEventArgs e)
{
lock (filesToProcess)
{
filesToProcess.Add(e.FullPath);
}
}
private void ProcessFile(FileStream fs)
{
// Your code here...
}
private void ProcessFilesTimer(object state)
{
string[] currentFiles;
lock (filesToProcess)
{
currentFiles = filesToProcess.ToArray();
}
foreach (string fileName in currentFiles)
{
TryProcessFile(fileName);
}
fileTimer.Change(PollInterval, Timeout.Infinite);
}
private void TryProcessFile(string fileName)
{
FileStream fs = null;
try
{
FileInfo fi = new FileInfo(fileName);
fs = fi.OpenRead();
}
catch (IOException)
{
// Possibly log this error
return;
}
using (fs)
{
ProcessFile(fs);
}
lock (filesToProcess)
{
filesToProcess.Remove(fileName);
}
}
}
(Note - I'm recalling this from memory here so it might not be perfect - let me know if it's buggy.)