Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

FileSystemWatcher vs Locked Files

4.75/5 (6 votes)
7 Dec 2017CPOL2 min read 16.6K  
You monitor a folder for new files with FileSystemWatcher and try to move those them on arrival. You get an IOException: ...being used by another process. The copying had not finished. Here, I offer a fairly robust solution to this.

Introduction

You’ve just written your nice shiny new application to monitor a folder for new files arriving and added the code to send that file off somewhere else and delete it. Perhaps you even spent some time packaging it in a nice Windows Service. It probably behaved well during debugging. You move it into a test environment and let the manual testers loose. They copy a file, your eager file watcher spots the new file as soon as it starts writing and does the funky stuff and BANG!… an IOException:

The process cannot access the file X because it is being used by another process.

The copying had not finished before the event fired. It doesn’t even have to be a large file as your super awesome watcher is just so efficient.

A Solution

  1. When a file system event occurs, store its details in Memory Cache for X amount of time
  2. Setup a callback, which will execute when the event expires from Memory Cache
  3. In the callback, check the file is available for write operations
  4. If it is, then get on with the intended file operation
  5. Else, put it back in Memory Cache for X time and repeat the above steps

It would make sense to track and limit the number of retry attempts to get a lock on the file.

Code Snippets

I’ve built this on top of the code discussed in a previous post on dealing with multiple FileSystemWatcher events.

The complete code for this example solution can be found here.

When a file system event is handled, store the file details and the retry count, using a simple POCO, in MemoryCache with a timer of, something like 60 seconds:

C#
private void OnCreated(object source, FileSystemEventArgs e)
{
    _cacheItemPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CacheTimeSeconds);

    var fileData = new CacheItemValue()
    {
         FilePath = e.FullPath,
         RetryCount = 0,
         FileName = e.Name
    };
    _memCache.AddOrGetExisting(e.Name, fileData, _cacheItemPolicy);
}

A simple POCO:

C#
class CacheItemValue
{
    public string FileName { get; set; }
    public string FilePath { get; set; }
    public int RetryCount { get; set; }
}

In the constructor, I initialised my cache item policy with a callback to execute when these cached POCOs expire:

C#
_cacheItemPolicy = new CacheItemPolicy
{
    RemovedCallback = OnRemovedFromCache
};

The callback itself…

  1. Increments the number retries
  2. Tries and gets a lock on the file
  3. If still locked, put it back into the cache for another 60 seconds (repeat this MaxRetries times)
  4. Else, get on with the intended file operation
C#
private void OnRemovedFromCache(CacheEntryRemovedArguments args)
{
    // Checking if expired, for a bit of future-proofing
    if (args.RemovedReason != CacheEntryRemovedReason.Expired) return;

    var cacheItemValue = (CacheItemValue)args.CacheItem.Value;
    if (cacheItemValue.RetryCount > MaxRetries) return;

    if (IsFileLocked(cacheItemValue.FilePath))
    {
        cacheItemValue.RetryCount++;
        _cacheItemPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CacheTimeSeconds);
              
        _memCache.Add(cacheItemValue.FileName, cacheItemValue, _cacheItemPolicy); 
     }

     // Now safe to perform the file operation here...
}

Other Ideas / To Do….

  • Could also store the actual event object
  • Could explore options for non-volatile persistence
  • Might find sliding expiration more appropriate in some scenario

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)