3

FileSystemWatcher events can fire multiple times. Not good if I need predictable behaviour from my code.

This is described in the MSDN documentation:

Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.

Good use of NotifyFilters with particular events has helped but won't give me 100% confidence in the consistency.

Here's an example, recreating a Notepad write example (but I have experienced this with other write actions too):

 public ExampleAttributesChangedFiringTwice(string demoFolderPath)
 {
     var watcher = new FileSystemWatcher()
     {
          Path = @"c:\temp",
          NotifyFilter = NotifyFilters.LastWrite,
          Filter = "*.txt"
      };

      watcher.Changed += OnChanged;
      watcher.EnableRaisingEvents = true;
  }

  private static void OnChanged(object source, FileSystemEventArgs e)
  {
      // This will fire twice if I edit a file in Notepad
  }

Any suggestions for making this more resilient?

EDIT: meaning not repeating multiple actions when multiple events are triggered.

Ben Hall
  • 1,353
  • 10
  • 19

2 Answers2

3

An approach utilising MemoryCache as a buffer that will 'throttle' additional events.

  1. A file event (Changed in this example) is triggered
  2. The event is handled by OnChanged but instead of completing the desired action, it stores the event in MemoryCache with a 1 second expiration and a CacheItemPolicy callback setup to execute on expiration.

Note that I use AddOrGetExisting as an simple way to block any additional events firing within the cache period being added to the cache.

  1. When it expires, the callback OnRemovedFromCache completes the behaviour intended for that file event

.

  class BlockAndDelayExample
{
    private readonly MemoryCache _memCache;
    private readonly CacheItemPolicy _cacheItemPolicy;
    private const int CacheTimeMilliseconds = 1000;

    public BlockAndDelayExample(string demoFolderPath)
    {
        _memCache = MemoryCache.Default;

        var watcher = new FileSystemWatcher()
        {
            Path = demoFolderPath,
            NotifyFilter = NotifyFilters.LastWrite,
            Filter = "*.txt"
        };

        _cacheItemPolicy = new CacheItemPolicy()
        {
            RemovedCallback = OnRemovedFromCache
        };

        watcher.Changed += OnChanged;
        watcher.EnableRaisingEvents = true;
    }

    // Add file event to cache for CacheTimeMilliseconds
    private void OnChanged(object source, FileSystemEventArgs e)
    {
        _cacheItemPolicy.AbsoluteExpiration =
            DateTimeOffset.Now.AddMilliseconds(CacheTimeMilliseconds);

        // Only add if it is not there already (swallow others)
        _memCache.AddOrGetExisting(e.Name, e, _cacheItemPolicy);
    }

    // Handle cache item expiring
    private void OnRemovedFromCache(CacheEntryRemovedArguments args)
    {
        if (args.RemovedReason != CacheEntryRemovedReason.Expired) return;

        // Now actually handle file event
        var e = (FileSystemEventArgs) args.CacheItem.Value;
    }
}

Could easily extend to:

  • Check file lock on expiry from cache and if not available, put it back in the cache again (sometimes events fire so fast the file isn't ready for some operations). Preferable to using try/catch loops.
  • Key cache on file name + event type combined
Ben Hall
  • 1,353
  • 10
  • 19
1

I use a FileSystemWatcher to check for MP4 files being uploaded that I ultimately have to do something with. The process doing the upload doesn't seem to establish any lock on the file, so I formerly struggled with starting the processing of them too early.

The technique I adopted in the end, which has been entirely successful for my case, was to consume the event and add the filepath to a Dictionary<string, long> of potentially interesting files, and start a timer. Periodically (60 seconds) I check the file size. The long dictionary value holds the file size from the last check, and if the current size is greater I deem it still being written to, store the new size and go back to sleep for another 60 seconds.

Upon there being a period of 60 seconds where no write activity has occurred, I can start processing.

If this isn't suitable for you, there are a few other things you could consider; hash the file every minute and store the hash instead, re-hash it periodically until the content hasn't changed. Keep tabs on the Last Modified date in the file system, perhaps

Ultimately, consider that FileSYstemWatcher might be a useful device not for notifying you which files you have to act on, but instead for files that are potentially interesting, and a separate process with more refined in-house logic can decide if a potentially interesting file should be acted on

Caius Jard
  • 72,509
  • 5
  • 49
  • 80