3

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;
        }
    }
Vicky
  • 2,027
  • 1
  • 19
  • 31
  • Not even sure why you are using `blockingCollection`... each time you hit `OnCreated` you start a new task to add to the collection, then you start another task to iterate over all the collection (which if this worked, would log each file again on subsequent calls). Why not just start a task to process the file? – crashmstr Jul 24 '15 at 12:06
  • 2
    `try{...}catch{throw;}` does nothing useful and can be removed. – juharr Jul 24 '15 at 12:13
  • @crashmstr-actually I used normal without blockingCollection but then the .exe i call was not able to process all file at once.So if i have 10 files thn the program was invoked 10 times for each file.It should be one time for one file..So I was suggested to use QUEUEs or blokingCollections – Vicky Jul 24 '15 at 12:17
  • @juharr-but Sir what if any file could not be processed,I used this so that could get exception if anything like this occurs. – Vicky Jul 24 '15 at 12:19
  • related [posts](http://stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-for-file-changes). spoiler: FSW is known for missing an event now and then.... – rene Jul 24 '15 at 12:23
  • @rene-without Blockingccollection,it never missed any file.But I need some queuing mechanism as other process cant handle so many file at once – Vicky Jul 24 '15 at 12:26
  • So your real problem is that the number of batch files that run concurrent should be limited? – rene Jul 24 '15 at 12:34
  • @Rene-Yes Sir,That why I used bLockingCollection as I need some queue mechanism.so it gives some time for batch to process file and then call it again for another file.I tried much and came up with this solution which is unfortunately failing me – Vicky Jul 24 '15 at 12:36

2 Answers2

2

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 ConcurrentQueue and ONE method that reads that queue. I also use the method WaitForExit on the process instance to prevent running more than one process.

    static ConcurrentQueue<string> filenames = new ConcurrentQueue<string>();

    static void QueueHandler()
    {
        bool run = true;
        AppDomain.CurrentDomain.DomainUnload += (s, e) =>
        {
            run = false;
            filenames.Enqueue("stop");
        };
        while(run)
        {
            string filename;
            if (filenames.TryDequeue(out filename) && run)
            {
                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.Factory.StartNew(QueueHandler);

        var fsw = new FileSystemWatcher();
        fsw.Created += (o, e) =>
            {
                // add a file to the queue
                filenames.Enqueue(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

rene
  • 41,474
  • 78
  • 114
  • 152
  • Well you are adding files to Queue, but where are you processing that file? When I debug this it is hitting this filenames.Enqueue(e.FullPath); but never getting to QueueHandler. Where can I write my logic to process that queued file? – HaBo Feb 21 '20 at 21:03
  • @HaBo I would expect `Task.Factory.StartNew(QueueHandler);` would take care of that. On what framework are you testing? Could it be the QueueHandler throws as exception somehow? – rene Feb 21 '20 at 21:09
  • I had to make QueueHandler static method then it is hitting. I am on .NET 4.7.2 Am I supposed to put my file processing login inside TryDequeue condition lIke if (_filenames.TryDequeue(out filename) && run) { _service.ProcessAndSaveInDb(filename); } Is this right locking? – HaBo Feb 21 '20 at 21:14
  • @HaBo yes, that looks right but do wrap that call in a try / catch to prevent the loop from exiting. And log any errors that are catched ... – rene Feb 21 '20 at 21:18
0

You are starting two tasks in your OnCreated method, where the second task seems to depend on the output from the first task. However, there is no guarantee that the first task will have finished (or even started) when the second task executes.

You could group the two operations into a single task, which would then execute sequentially, or you could await the result of the first task.

There is also a lot of information missing from your code. It clearly isn't the 'real' code because OnDeleated [sic] is misspelled and wouldn't compile. We also can't see what your external process is or how you are attempting to pass the file list to it. There could be lots of problems there. Would it be possible to post the actual code?

Tim Long
  • 13,508
  • 19
  • 79
  • 147
  • I think your First reason can be perfectly valid here as sometime only 3-4 files are proceesed and sometime more than that and some time only one file gets processed by batch file.Will try one more thing(Parallel Invoke) thats coming to my mind.I added the actual code as you suggested – Vicky Jul 24 '15 at 13:05
  • Sir,i tried with parallel invoke,still same problem.and Are you suggesting that I should use synchronous thread (one to add file in Queue and other to read).But there is no fix limit number of file that comes.It is implemented in FileSystemWatcher,it keeps coming .So what could be the size of queue.Sorry to bother you but no experience in threding – Vicky Jul 24 '15 at 14:33
  • I would forget about threading until you are sure everything is working synchronously. Once you have that going, then maybe use a task to process the files, but only if there is a clear need to do so. Threading is incredibly hard to get right! As other people are saying, layering more threading constructs on top of a non-working algorithm are just going to increase the chances for further bugs. – Tim Long Jul 24 '15 at 16:42