0

I'm using filesystemwatcher for a program that will run on framework 1.1 and when a change is happened it sends 2 signals saying its changed. From research I'm aware this is just how windows works and theres nothing I can do to stop this with the fsw.

But as a work around I would like to make it so it accepts the first pulse, then locks off so the other one trying to call the method is ignored (or redirected?) as I've got a backup system with it and it is making 2 copies of all the files so it's something I really need to address. Elsewhere in the code is effected and I've managed to use timers to fix this by blocking off the timer as soon as its called however in this instance it'll get quite messy and I'm sure there has to be a cleaner solution.

code:

private static void GetCurrentJob()
        { 
            ///Lots of code that isn't relevant 
        }
private static void ProgramSwapMonitor(string ProgramChange)
        {
            // Create a new FileSystemWatcher and set its properties.
            FileSystemWatcher watcher = new FileSystemWatcher
            {
                Path = ProgramChange,

                /* Watch for changes in LastAccess and LastWrite times, and 
                   the renaming of files or directories. */

                NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
               | NotifyFilters.FileName | NotifyFilters.DirectoryName,

                // Only watch this specific file
                Filter = "dnocontainer.cfg"
            };

            // Add event handlers.
            watcher.Changed += new FileSystemEventHandler(OnChanged);
            watcher.Created += new FileSystemEventHandler(OnChanged);
            watcher.Deleted += new FileSystemEventHandler(OnChanged);
            watcher.Renamed += new RenamedEventHandler(OnRenamed);

            // Begin watching.
            watcher.EnableRaisingEvents = true;
        }
        // Define the event handlers.
        private static void OnChanged(object source, FileSystemEventArgs e)
        {

            //This disables the directory monitor, then changes the active job in its memory, then restarts the directory monitor so it can now monitor the new location, then removes the old watcherChanged instance so theres no duplicates.
            fileSystemWatcher.EnableRaisingEvents = false;
            GetCurrentJob(); //This is the method that needs to be only running once, but is made to run twice
            MonitorDirectory(path);
            fileSystemWatcher.Changed -= new FileSystemEventHandler(FileSystemWatcher_Changed);
            
        }
  • Do you want to receive a fisrt watcher event, next block all overs while doing something and reactivate the monitoring after finished ? –  Jul 15 '20 at 11:10
  • @OlivierRogier I was having issues with the amount of monitoring events increasing so like now it does it twice because of windows. Before it would do it twice, then three times, then four times..so on. By disabling and removing an instance seems to have fixes this. You might be able to tell, quite bad at coding, I imagine there was a much better solution but google didn't help and when I tried this and it worked, I was content. – Simpson.Joshua Jul 15 '20 at 11:13
  • You may be interested in a [`OnAnyEvent`](https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice/58079327#58079327) extension method that subscribes to multiple `FileSystemWatcher` events, and invokes a handler after a delay. It is similar in functionality with Enigmativity's [Rx solution](https://stackoverflow.com/a/62933669/11178549). – Theodor Zoulias Jul 16 '20 at 13:04

2 Answers2

3

A simple way is to use a conditional variable:

static private bool WatcherMutex;

static private void OnChanged(object source, FileSystemEventArgs e)
{
  if ( WatcherMutex ) return;
  WatcherMutex = true;
  try
  {
    ...
  }
  finally
  {
    WatcherMutex = false;
  }
}

Another way is to add and remove the handlers from the watcher to relieve the process of the pool of events but this requires a class field instead of a local var:

static private FileSystemWatcher Watcher = new FileSystemWatcher();
static private void SetWatcherHandlers(bool active)
{
  if ( active )
  {
    Watcher.Changed += OnChanged;
    Watcher.Created += OnChanged;
    Watcher.Deleted += OnChanged;
    Watcher.Renamed += OnRenamed;
  }
  else
  {
    Watcher.Changed -= OnChanged;
    Watcher.Created -= OnChanged;
    Watcher.Deleted -= OnChanged;
    Watcher.Renamed -= OnRenamed;
  }
}
static private void OnChanged(object source, FileSystemEventArgs e)
{
  SetWatcherHandlers(false);
  try
  {
    ...
  }
  finally
  {
    SetWatcherHandlers(true);
  }
}
static private void ProgramSwapMonitor(string ProgramChange)
{
  Watcher = new FileSystemWatcher
  {
    Path = ProgramChange,
    NotifyFilter = NotifyFilters.LastAccess 
                  | NotifyFilters.LastWrite
                  | NotifyFilters.FileName 
                  | NotifyFilters.DirectoryName,
    Filter = "dnocontainer.cfg"
  };
  SetWatcherHandlers(true);
  Watcher.EnableRaisingEvents = true;
}

Or you can simply enable and disable the watcher itself using the same class flield:

static private void OnChanged(object source, FileSystemEventArgs e)
{
  Watcher.EnableRaisingEvents = false;
  try
  {
    ...
  }
  finally
  {
    Watcher.EnableRaisingEvents = true;
  }
}

You can use the way that matches the better to your needs to enable and disable the entire watcher using EnableRaisingEvents or only one or more handler at a time using some conditional variables or some SetWatcherHandlerXXXX one by one or more, or add/remove handler(s) directly in the handler method instead of a conditional variable:

static private void OnChanged(object source, FileSystemEventArgs e)
{
  Watcher.Changed -= OnChanged;
  Watcher.Created -= OnChanged;
  Watcher.Deleted -= OnChanged;
  try
  {
    ...
  }
  finally
  {
    Watcher.Changed += OnChanged;
    Watcher.Created += OnChanged;
    Watcher.Deleted += OnChanged;
  }
}

Thus here we consider OnChanged behavior and OnRenamed as different.

  • The first method you suggested with the Boolean hasn't worked. And the final method hasn't worked either. Same issue where whats happening in my GetCurrentJob method is running twice. Is there no way of blocking that method inside itself? – Simpson.Joshua Jul 15 '20 at 11:30
  • Thus I don't understand your problem and your question. All methods must block any triggering from the watcher, allowing you to do a processing without being disturbed. And FileSystemWatcher is not multhreaded, so here you can easily prevent reentrance in the same thread, with a boolean for each sort of event or group you need, or by changing all with EnableRaisingEvents, or by playing with add/remove events on the watcher. –  Jul 15 '20 at 11:32
  • 1
    Just a small note - methods named `On*` are, by convention, the methods that **raise** an event, and not the **handler** of an event. Handlers are typically named `Watcher_Changed`, i.e. the object name, underscore, the event name. – Enigmativity Jul 15 '20 at 11:42
  • @Enigmativity The OP has called the method `OnChanged` instead of `Watcher_Changed` that is not conventionnal in C#, but the standard, for example, in the Delphi VCL where the method that raises the event would be called `DoChange`. But indeed, I got tangled up by mixing the words handler and event, I corrected that, thank you. –  Jul 15 '20 at 11:44
  • @OlivierRogier Using watcher.Changed instead is complained at saying its not in the name space. Also, the reason behind doing Onchanged rather than the typical method is this is all having to be done in framework 1.1, which the FSW class seems to be slightly sketchy with. Hence why my original question wanted to let the method be called twice like it is but instead once the first one calls it, be blocked/locked off so the second pulse trying to can't. Having a small delay between the method being called I thought would solve it but don't know how. – Simpson.Joshua Jul 15 '20 at 12:13
  • Indeed `watcher` does not exist because in the suggested code I called it `Watcher` and it is declared as a private field member of the class instead of a local variable. I'm sorry, I don't understand what you wrote after because of my poor english. –  Jul 15 '20 at 12:18
  • I did solve it by setting up a t_Elapsed timer. In GetCurrentJob I've got all the code inside an if. ```if(BlockerB == true { BlockerB= false; //then I start my timer```. Inside the timer has BlockerB =True. – Simpson.Joshua Jul 15 '20 at 12:51
0

You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

IDisposable subscription =
    Observable
        .Merge(
            Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Changed += h, h => watcher.Changed -= h),
            Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Created += h, h => watcher.Created -= h),
            Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Deleted += h, h => watcher.Deleted -= h))
        .GroupBy(x => x.EventArgs.FullPath)
        .Select(gxs => gxs.Throttle(TimeSpan.FromSeconds(0.25)))
        .Merge()
        .Subscribe(x => GetCurrentJob());

Now, what does that do?

  • First, it merges all three events (Changed, Created, and Deleted) into a single stream of events.

  • Then, it does a GroupBy on the FullName so that each individual change to any file is treated as if that file had its own event.

  • Then it throttles the events for each FullName so that any event is ignored if another event for that FullName occurs within 0.25 seconds.

  • Then it merges all of the FullName events into a single stream of events.

  • Finally, it calls GetCurrentJob on each, one at a time.

This eliminates a lot of your concerns about pausing events, etc.

If you want to stop the events, just call subscription.Dispose().

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • According to NuGet Gallery this isn't supported in framework 1.1 unfortunately. However it does look like it is what I was looking for so thank you, I will keep this close for any future projects. I've got a solution now anyway with timers so all is well. Cheers! – Simpson.Joshua Jul 17 '20 at 07:43
  • @Simpson.Joshua - You're on Core 1.1? I thought it was supported from the beginning... – Enigmativity Jul 17 '20 at 12:33
  • Framework 1.1 not core, but apparently it was introduced in 2.0. Which is effectively the beginning really as nothing really uses 1.1 since 2006. However my company uses very old outdated machinery and PC's to suit. – Simpson.Joshua Jul 19 '20 at 05:20