Search code examples
c#multithreadingfilesystemwatcher

FileSystemWatcher losing files in its queue


I have written a FileSystemWatcher to call a pgm once for every file. But some of my files are lost. I tested the code with only 10-11 files. Deletion of a file is logged correctly, but not the creation. Some of the files are not logged. Is there maybe any problem in my TASK implementation? or is there any problem with Window Service?

 public static FileSystemWatcher m_Watcher;
        static BlockingCollection<string> blockingCollection = new BlockingCollection<string>();

protected override void OnStart(string[] args)
    {

        current_directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        //XmlDocument xml = new XmlDocument();
        try
        {
            strDir = ConfigurationManager.AppSettings["Directory"];
            fileMask = ConfigurationManager.AppSettings["FileMask"];
            strBatfile = ConfigurationManager.AppSettings["Batch"];
            strlog = ConfigurationManager.AppSettings["Log"];

            m_Watcher = new FileSystemWatcher();


            m_Watcher.Filter = fileMask;
            m_Watcher.Path = strDir + "\\";
            m_Watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                             | NotifyFilters.FileName | NotifyFilters.DirectoryName;




            m_Watcher.Created += new FileSystemEventHandler(OnCreated);

            m_Watcher.Deleted += new FileSystemEventHandler(OnDeleated);
            m_Watcher.Renamed += new RenamedEventHandler(OnRenamed);


            m_Watcher.EnableRaisingEvents = true;
        }
        catch (Exception exception)
        {
            CustomException.Write(CustomException.CreateExceptionString(exception.ToString()));
        }

    }
    public static void OnDeleated(object source, FileSystemEventArgs e)
    {
        try
        {

            Log.getLogger("File deleated- Filename :" + e.Name + " at timestamp : " + DateTime.Now.ToString(), strlog);
        }
        catch (Exception exception)
        {
            CustomException.Write(CustomException.CreateExceptionString(exception, e.Name));
        }
    }

    private static void OnCreated(object source, FileSystemEventArgs e)
    {

        var exceptions = new ConcurrentQueue<Exception>();

        try
        {

            Task.Factory.StartNew(() =>
            {
                try
                {

                    blockingCollection.Add(e.Name.ToString());

                }
                catch (Exception)
                {
                    throw;

                }

            });
            Task.Factory.StartNew(() =>
            {
                try
                {


                    foreach (string value in blockingCollection.GetConsumingEnumerable())
                    {
                        System.Diagnostics.Process.Start(Service1.strBatfile);
                        Log.getLogger("File Processed after executing batch:  Filename ->:" + value + " " + "Batch File Executed- > " + Service1.strBatfile + " at timestamp : " + DateTime.Now.ToString(), Service1.strlog);

                    }
                }
                catch (Exception)
                {
                    throw;
                }



            });


        }
        catch (AggregateException ae)
        {

            foreach (var ex in ae.InnerExceptions)
            {
                CustomException.Write(CustomException.CreateExceptionString(ex, e.Name));
            }
        }
        finally
        {
            m_Watcher.EnableRaisingEvents = true;
        }
    }

Solution

  • You are using way to many threads/Tasks to get a clear understanding how the code works. As you stated that you want to only process one file at a time you need just one Thread/Task that lives for the lifetime of the class (and I assume the application).

    If I strip down your code to accomplish processing one file at a time whenever a file is dropped in a certain folder this could be one implementation.

    Notice how I have one BlockingCollection and ONE method that reads that queue. Notice how GetConsumingEnumerable() helps us here with blocking the thread. I also use the method WaitForExit on the process instance to prevent running more than one process.

        static  BlockingCollection<string> filenames = new  BlockingCollection<string>();
    
        static void QueueHandler()
        {
            AppDomain.CurrentDomain.DomainUnload += (s, e) =>
            {
                filenames.CompleteAdding();
            };
            foreach(var filename in filenames.GetConsumingEnumerable()) {
                 var proc = new Process();
                 proc.StartInfo.FileName = filename;
                 proc.Start();
                 proc.WaitForExit(); // this blocks until the process ends....
            }
        }
    

    Now we need a single Task/Thread that will run QueueHandler and our FileSystemWatcher:

    protected override void OnStart(string[] args)
    {
            // have our queue reader method started
            Task.Run(QueueHandler);
    
            var fsw = new FileSystemWatcher();
            fsw.Created += (o, e) =>
                {
                    // add a file to the queue
                    if (!filenames.IsCompleted) 
                    {
                       filenames.Add(e.FullPath);
                    }
                    // optionally add polling for missed files
                    // http://stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-for-file-changes
                };
    
            fsw.Path = ConfigurationManager.AppSettings["Directory"];
            fsw.NotifyFilter = NotifyFilters.FileName;
            fsw.Filter = ConfigurationManager.AppSettings["FileMask"];
    
            fsw.EnableRaisingEvents = true;
    }
    

    This implementation will use at worst three threads: one main thread, one for the Created events of the FileSystemWatcher and one for the QueueHandler instead if your example code where new Tasks were started every time a new file was created in the folder the FileSystemWatcher was watching