3

I have the following code that i use to monitor a directory for text files, the directory gets new files twice a day, the code works fine for sometime but after that it stops firing OnCreated event...

[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
public static void Run()
{
    FileSystemWatcher watcher = new FileSystemWatcher();
    watcher.Path = @"c:\users\documents\";

    watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
       | NotifyFilters.FileName | NotifyFilters.DirectoryName;

    watcher.Filter = "*.txt";

    // Add event handlers.
    watcher.Created += new FileSystemEventHandler(OnCreated);

    // Begin watching.
    watcher.EnableRaisingEvents = true;

    // Wait for the user to quit the program.
    Console.WriteLine("Press \'q\' to quit the sample.");
    while(Console.Read()!='q');
}

private static void OnCreated(object source, FileSystemEventArgs e)
{
   Console.WriteLine("File: " +  e.FullPath + " " + e.ChangeType);
}

Cannot figure out the issue.

Also, I would like to know a foolproof alternative to this (if any), since I am not finding this reliable..

Rajat Suneja
  • 494
  • 2
  • 10
  • 27
  • Note that `FileSystemWatcher` has a bit of a reputation for being unreliable (though I'm not sure if this is a problem with `FileSystemWatcher`'s implementation or with how it's being used). See http://stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-for-file-changes for suggestions on how others have dealt with the problem. – Michael Burr Jan 07 '14 at 18:05
  • Also, my understanding is that `FileSystemWatcher` uses the system's `ReadDirectoryChangesW` API. It might help to read this article by Jim Beveridge on the details of that API: http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html – Michael Burr Jan 07 '14 at 18:08

3 Answers3

8

Because after the Run method has completed, watcher is eligible for garbage collection. This means that after some time watcher will be collected and obviously will stop raising events.

To solve, keep a reference of the watcher in the outer scope:

private static FileSystemWatcher watcher;

public static void Run()
{
    watcher = new FileSystemWatcher();
    ...
}
Alberto
  • 15,626
  • 9
  • 43
  • 56
  • Not sure that the reason given is correct. The Run method doesn't exit until someone press the letter 'q' and so the local variable are still valid and not eligible for GC. Am I wrong? – Steve Jan 06 '14 at 18:46
  • 3
    @Steve: a reference's lifetime is not necessarily extended by a variable's scope, only by whether or not a reference is 'active'. Since the `watcher` local variable is no longer used after the `watcher.EnableRaisingEvents = true;` the GC may consider it inactive and may collect it. – Michael Burr Jan 06 '14 at 18:49
  • @MichaelBurr can you give me some reference (no puns intended) to this, because, as I see it, the local variable watcher (until exit from the Run method) still references a valid object in the heap and the GC should not destroy an object when there is still a reference to the object active. – Steve Jan 06 '14 at 18:57
  • @Steve: I'm having a hard time coming up with a good reference. I thought there were several articles that discussed how timers with only local variable references wouldn't fire because they were GC'd earlier than expected. I also thought that MS changed at least one timer class to maintain references to enabled timers in a private static list until they fired to fix that problem (though I can't find details on that, so I might be remembering wrong). – Michael Burr Jan 06 '14 at 19:22
  • 1
    @Steve: However, if you read the remarks on the `System.Timers.Timer` class MSDN docs, http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx, you'll see: "The code contains declarations of the timer variable at the class level and inside Main. To see how aggressive garbage collection can affect a timer that is declared inside a long-running method, you can comment out the class-level declaration and uncomment the local variable. To keep the timer from being collected, uncomment the GC.KeepAlive method at the end of Main", which implies that `aTimer` can be GC'd before the scope end. – Michael Burr Jan 06 '14 at 19:24
  • Here's Lippert's take on the GC subject: `The C# specification states that the runtime is permitted broad latitude to detect when storage containing a reference is never going to be accessed again...` https://ericlippert.com/2015/05/21/when-everything-you-know-is-wrong-part-two/ – Chad Schouggins Jan 17 '17 at 21:31
  • Besides being something I've had experience with through the years, and so can confirm, this behaviour is also documented here: https://learn.microsoft.com/en-us/dotnet/api/system.gc.keepalive?view=net-6.0 – Bent Tranberg Dec 14 '21 at 14:12
6

The Problem with this was that the reference to the FILESYSTEMWATCHER was being collected by the GC and hence after sometime the FILEWATCHER had a null reference leading to events not getting raised.

Solution :-

private static FileSystemWatcher watcher;
public static void Run()
{
watcher = new FileSystemWatcher();
...
GC.KeepAlive(watcher);  
}

Just keeping a reference of the watcher in the outer scope as suggested did not solve the problem. I had be explicitly specify that GC should not collect the FileWatcher object.

Rajat Suneja
  • 494
  • 2
  • 10
  • 27
  • The reason for that is possibly that the object containing the reference was also garbage collected. We'd have to see the code to know for sure. – Bent Tranberg Dec 14 '21 at 14:15
0

Assuming your watcher isn't going out of scope, there's you can check for any errors that occur such as running out of the watchers internal buffer... watcher.Error += Watcher_Error; ... private void Watcher_Error(object sender, ErrorEventArgs e) { Debug.WriteLine("Watcher_Error: " + e.GetException().Message); }

angrywill
  • 11
  • 2