0

I have created a relatively simple windows app that watches a folder for files. When a new file is created into the folder the app (via FileSystemWatcher) will open the file and process the contents. Long story short, the contents are used with Selenium to automate a web page via IE11. This processing takes about 20 seconds per file.

The problem is if more than one file is created into the folder at roughly the same time or when the app is processing a file, FileSystemWatcher onCreated does not see the next file. So when processing completes on the first file the app just stops. Meanwhile there is a file in the folder that does not get processed. If a file is added after the onCreated processing is finished it works fine and processes that next file.

Can someone please guide me towards what I should be looking at to solve this? Excessive detail is very welcome.

rene
  • 41,474
  • 78
  • 114
  • 152
Andy
  • 383
  • 1
  • 6
  • 23
  • 3
    Uh, a answer is not easy. Best way is a producer- and consumer pattern with a queue (FIFO). – akop Jan 11 '18 at 21:10
  • user6537157, I have no idea how to do what you've suggested. – Andy Jan 11 '18 at 21:11
  • Google for "c# queue". FSW adds filename to queue. And a worker (that process the files) only dequeue the filenames from queue. A answer is not easy. :P – akop Jan 11 '18 at 21:16
  • First thing that coomes to mind is to process those files in different threads, so you don't block the FSW thread and the notifications are queued as normal. – Jimi Jan 11 '18 at 21:23
  • 1
    In my experience the filesystemwatcher is very unreliable because the events raised by it depend on how applications create modify and delete files. In general I now prefer a polling mechanism over the fsw as long as it is relatively cheap to determine the changes. Polling is also more reliable when file changes occur when the fsw is not watching. Side note: Make sure the handling of the files is idempotent so the application handles restarts and re-uploads correctly – Emond Jan 16 '18 at 15:52
  • @Erno, could you provide me with a small working example? – Andy Jan 16 '18 at 16:13
  • 1
    @Andy, that would not be appropriate to your question as the question is about the file system watcher. I would create a thread that is looping for as long as required. On every iteration: process the file(s), then sleep for a little while. – Emond Jan 16 '18 at 16:48
  • @ErnodeWeerd, could you please help me with this issue then? [https://stackoverflow.com/questions/48288816/need-to-create-simple-c-sharp-windows-app-looks-in-folder-and-processes-new-fi](https://stackoverflow.com/questions/48288816/need-to-create-simple-c-sharp-windows-app-looks-in-folder-and-processes-new-fi) – Andy Jan 16 '18 at 19:52
  • @Andy, I have an exact same issue as yours. Were you able to figure it out? If so, please post your solution. Thank you in advance. – dailyUnknown Aug 18 '22 at 17:11

3 Answers3

5

FileSystemWatcher (as you've already noticed) is not reliable ,you will always have to add a "custom"/manual logic for missing files (also,note that you might see more than one event for the same file)

Below you can see a simple example with a "background" check for unprocessed file.
You could avoid the locks by using concurrent collections e.g BlockingCollection
You could also chose to process your files in parallel
I'm processing the file based on a timer but you could use your own strategy.
If you don't want to process the file ,in real time ,probably you don't even need the FileSystemWatcher

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace ConsoleAppDemo
{
    class Program
    {
        private static object lockIbj = new object();
        private static List<string> _proccessedFiles = new List<string>();
        private static readonly List<string> toProccessFiles = new List<string>();
        private static List<string> _proccessingFiles = new List<string>();
        private const string directory = @"C:\Path";
        private const string extension = @"*.txt";
        static void Main(string[] args)
        {
            FileSystemWatcher f = new FileSystemWatcher();
            f.Path = directory;
            f.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                             | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            f.Filter = extension ;
            f.Created += F_Created;
            f.EnableRaisingEvents = true;

            Timer manualWatcher = new Timer(ManuallWatcherCallback, null, 0, 3000);

            Timer manualTaskRunner = new Timer(ManuallRunnerCallback, null, 0, 10000);

            Console.ReadLine();
        }

        private static void F_Created(object sender, FileSystemEventArgs e)
        {
            lock (lockIbj)
            {
                toProccessFiles.Add(e.FullPath);
                Console.WriteLine("Adding new File from watcher: " + e.FullPath);
            }

        }

        private static void ManuallWatcherCallback(Object o)
        {
            var files = Directory.GetFiles(directory, extension);
            lock (lockIbj)
            {
                foreach (var file in files)
                {
                    if (!_proccessedFiles.Contains(file) && !toProccessFiles.Contains(file) && !_proccessingFiles.Contains(file))
                    {
                        toProccessFiles.Add(file);
                        Console.WriteLine("Adding new File from manuall timer: " + file);
                    }
                }

            }
        }

        private static bool processing;
        private static void ManuallRunnerCallback(Object o)
        {
            if (processing)
                return;

            while (true)
            {
                //you could proccess file in parallel
                string fileToProcces = null;

                lock (lockIbj)
                {
                    fileToProcces = toProccessFiles.FirstOrDefault();
                    if (fileToProcces != null)
                    {
                        processing = true;
                        toProccessFiles.Remove(fileToProcces);
                        _proccessingFiles.Add(fileToProcces);
                    }
                    else
                    {
                        processing = false;
                        break;


                    }
                }

                if (fileToProcces == null)
                    return;

                //Must add error handling
                ProccessFile(fileToProcces);
            }
        }

        private static void ProccessFile(string fileToProcces)
        {
            Console.WriteLine("Processing:" + fileToProcces);
            lock (lockIbj)
            {
                _proccessingFiles.Remove(fileToProcces);
                _proccessedFiles.Add(fileToProcces);
            }
        }
    }
}
George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • 1
    This is similar to what I normally do in such a case (I also wrote some services which watches for files and processes them). One optimization I would do however: rewrite the consumer logic (around ManuallRunnerCallback) so that it immediately processes the file and does not need to wait for the next periodic call. – Rainer Schaack Jan 20 '18 at 18:02
  • @RainerSchaack Of course,you can start processing the file in the filewatcher event – George Vovos Jan 20 '18 at 18:12
0

Instead of using FileSystemWatcher, you can use P/Invoke to run the Win32 File System Change Notification functions, and loop through the file system changes as they occur:

[DllImport("kernel32.dll", EntryPoint = "FindFirstChangeNotification")]
static extern System.IntPtr FindFirstChangeNotification (string lpPathName, bool bWatchSubtree, uint dwNotifyFilter);

[DllImport("kernel32.dll", EntryPoint = "FindNextChangeNotification")]
static extern bool FindNextChangeNotification (System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "FindCloseChangeNotification")]
static extern bool FindCloseChangeNotification (System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
static extern uint WaitForSingleObject (System.IntPtr handle, uint dwMilliseconds);

[DllImport("kernel32.dll", EntryPoint = "ReadDirectoryChangesW")]
static extern bool ReadDirectoryChangesW(System.IntPtr hDirectory, System.IntPtr lpBuffer, uint nBufferLength, bool bWatchSubtree, uint dwNotifyFilter, out uint lpBytesReturned, System.IntPtr lpOverlapped, ReadDirectoryChangesDelegate lpCompletionRoutine);

Basically, you call FindFirstChangeNotification with the directory you want to monitor, which gives you a Wait Handle. You then call WaitForSingleObject with the handle, and when it returns, you know that one or more changes has occurred. Then, you call ReadDirectoryChangesW to find out what has changed, and process the changes. Calling FindNextChangeNotification gives you a handle to wait on for the next change to the file system, so you'll likely call this, then call WaitForSingleObject, and then call ReadDirectoryChangesW in a loop. When you're done, you call FindCloseChangeNotification to stop tracking changes.

EDIT: Here is a more complete example:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

[DllImport("kernel32.dll", EntryPoint = "FindFirstChangeNotification")]
static extern System.IntPtr FindFirstChangeNotification(string lpPathName, bool bWatchSubtree, uint dwNotifyFilter);

[DllImport("kernel32.dll", EntryPoint = "FindNextChangeNotification")]
static extern bool FindNextChangeNotification(System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "FindCloseChangeNotification")]
static extern bool FindCloseChangeNotification(System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
static extern uint WaitForSingleObject(System.IntPtr handle, uint dwMilliseconds);

[DllImport("kernel32.dll", EntryPoint = "ReadDirectoryChangesW")]
static extern bool ReadDirectoryChangesW(System.IntPtr hDirectory, System.IntPtr lpBuffer, uint nBufferLength, bool bWatchSubtree, uint dwNotifyFilter, out uint lpBytesReturned, System.IntPtr lpOverlapped, IntPtr lpCompletionRoutine);

[DllImport("kernel32.dll", EntryPoint = "CreateFile")]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

enum FileSystemNotifications
{
    FileNameChanged = 0x00000001,
    DirectoryNameChanged = 0x00000002,
    FileAttributesChanged = 0x00000004,
    FileSizeChanged = 0x00000008,
    FileModified = 0x00000010,
    FileSecurityChanged = 0x00000100,
}

enum FileActions
{
    FileAdded = 0x00000001,
    FileRemoved = 0x00000002,
    FileModified = 0x00000003,
    FileRenamedOld = 0x00000004,
    FileRenamedNew = 0x00000005
}

enum FileEventType
{
    FileAdded,
    FileChanged,
    FileDeleted,
    FileRenamed
}

class FileEvent
{
    private readonly FileEventType eventType;
    private readonly FileInfo file;

    public FileEvent(string fileName, FileEventType eventType)
    {
        this.file = new FileInfo(fileName);
        this.eventType = eventType;
    }

    public FileEventType EventType => eventType;
    public FileInfo File => file;
}

[StructLayout(LayoutKind.Sequential)]
struct FileNotifyInformation
{
    public int NextEntryOffset;
    public int Action;
    public int FileNameLength;
    public IntPtr FileName;
}

class DirectoryWatcher
{
    private const int MaxChanges = 4096;
    private readonly DirectoryInfo directory;

    public DirectoryWatcher(string dirPath)
    {
        this.directory = new DirectoryInfo(dirPath);
    }

    public IEnumerable<FileEvent> Watch(bool watchSubFolders = false)
    {
        var directoryHandle = CreateFile(directory.FullName, 0x80000000, 0x00000007, IntPtr.Zero, 3, 0x02000000, IntPtr.Zero);    
        var fileCreatedDeletedOrUpdated = FileSystemNotifications.FileNameChanged | FileSystemNotifications.FileModified;
        var waitable = FindFirstChangeNotification(directory.FullName, watchSubFolders, (uint)fileCreatedDeletedOrUpdated);
        var notifySize = Marshal.SizeOf(typeof(FileNotifyInformation));

        do
        {
            WaitForSingleObject(waitable, 0xFFFFFFFF); // Infinite wait
            var changes = new FileNotifyInformation[MaxChanges];
            var pinnedArray = GCHandle.Alloc(changes, GCHandleType.Pinned);
            var buffer = pinnedArray.AddrOfPinnedObject();
            uint bytesReturned;            

            ReadDirectoryChangesW(directoryHandle, buffer, (uint)(notifySize * MaxChanges), watchSubFolders, (uint)fileCreatedDeletedOrUpdated, out bytesReturned, IntPtr.Zero, IntPtr.Zero);

            for (var i = 0; i < bytesReturned / notifySize; i += 1)
            {
                var change = Marshal.PtrToStructure<FileNotifyInformation>(new IntPtr(buffer.ToInt64() + i * notifySize));

                if ((change.Action & (int)FileActions.FileAdded) == (int)FileActions.FileAdded)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileAdded);
                }
                else if ((change.Action & (int)FileActions.FileModified) == (int)FileActions.FileModified)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileChanged);
                }
                else if ((change.Action & (int)FileActions.FileRemoved) == (int)FileActions.FileRemoved)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileDeleted);
                }
                else if ((change.Action & (int)FileActions.FileRenamedNew) == (int)FileActions.FileRenamedNew)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileRenamed);
                }
            }

            pinnedArray.Free();
        } while (FindNextChangeNotification(waitable));

        FindCloseChangeNotification(waitable);
    }
}

var watcher = new DirectoryWatcher(@"C:\Temp");

foreach (var change in watcher.Watch())
{
    Console.WriteLine("File {0} was {1}", change.File.Name, change.EventType);
}
Aaron M. Eshbach
  • 6,380
  • 12
  • 22
  • Aaron, thank you very much for this alternate solution. Could ask a huge favor of you to show me a very simple stick man working example of your suggestion? That would help me immensely. I can then take your example and expand it on my own. :D – Andy Jan 12 '18 at 14:21
  • @Andy, I have edited the post to include an example `DirectoryWatcher` class. – Aaron M. Eshbach Jan 12 '18 at 16:12
  • Wow! Thanks Aaron. I will look at this today and get my program going. That's awesome! Thank you so much! :D – Andy Jan 12 '18 at 17:54
  • 3
    `FileSystemWatcher()` class is a wrapper for `ReadDirectoryChangesW` and its SMB messages. Here, you are just substituting the managed class with unmanaged P/Invokes to achieve something that somewhat resembles it's base implementation, but is lacking the SecurityCritical Attributes that the managed class handles. I think you should focus more on how to avoid the piling up of the FSW events when the OP's code blocks the main thread while processing those files and how to handle file access errors if the file notified is still locked by the process that created/modified it. – Jimi Jan 13 '18 at 22:24
  • @Aaron, I was able to get the program running without the previous error. When it runs and I put a file into the c:\temp folder I get the following error. Please see the link to the picture [here](https://i.imgur.com/vnvmW6d.png). Please, please help me if you can. – Andy Jan 16 '18 at 18:53
0

I have done this before but do not have the source code (previous job) and I ran into the same problem. I wound up creating a BackgroundWorker instance that would check the folder for new files. I would process the files, then archive them into a subfolder. Not sure if that's a possibility or not.

If moving the files is not an option, the BackgroundWorker might still be the answer. Track the LastModifiedDate or CreatedDate of the file and process any newer. In your onCreated, you would create an instance of BackgroundWorker and have it DoWork on the file. With your processing taking 20 seconds, I'm assuming you have all that logic directly called in the onCreated event logic. By taking it to another thread, you can do near instant processing and be done, while the other thread churns away until it's done.

Mr. Anderson
  • 165
  • 2
  • 10