0

If runs as Release occurs events only from 1st directory. No matter which is the first. In debug mode works properly.

public class Program
{
    private static void Main(string[] args)
    {
        string[] paths = File.ReadAllLines("config.txt");
        List<FolderWatch> lw = new List<FolderWatch>();
        foreach (string path in paths)
            if (Path.IsPathFullyQualified(path))
                lw.Add(new FolderWatch(path));
        Thread.Sleep(Timeout.InfiniteTimeSpan);
    }
}
public class FolderWatch
{
    private FileSystemWatcher watcher;
    public FolderWatch(string path)
    {
        watcher = new FileSystemWatcher();
        watcher.Path = path;
        watcher.Created += OnCreated;
        watcher.EnableRaisingEvents = true;            
    }
    private static void OnCreated(object source, FileSystemEventArgs e)
    {
            try
            {
                File.AppendAllText("log.txt", "Event occured");
            }
            catch { }
    }
}

Updated: I did some changes, regarding the scope of watcher.

string[] paths = File.ReadAllLines(configpath);

FileSystemWatcher[] w_arr = new FileSystemWatcher[paths.Length];

This works:

w_arr[0] = new FileSystemWatcher();
if (Path.IsPathFullyQualified(paths[0]))
    SetupWatcher(w_arr[0], paths[0]);

w_arr[1] = new FileSystemWatcher();
if (Path.IsPathFullyQualified(paths[1]))
    SetupWatcher(w_arr[1], paths[1]);

In a cycle it doesn't work. Only events from 1st directory occurs.

for (int i = 0; i < paths.Length; i++)
{
    if (Path.IsPathFullyQualified(paths[i]))
    {
        w_arr[i] = new FileSystemWatcher();
        SetupWatcher(w_arr[i], paths[i]);
    }
}

Finally thread sleeps and waits for events.

Thread.Sleep(Timeout.InfiniteTimeSpan);

private static void SetupWatcher(FileSystemWatcher watcher, string path)
{
    watcher.Path = path;
    watcher.EnableRaisingEvents = true;
    watcher.Filter = "*.pdf";
    watcher.Created += OnCreated;
    watcher.Error += OnError;
    GC.KeepAlive(watcher);
}
petr.f77
  • 29
  • 5
  • Sorry but cannot reproduce. What exactly do you do in _When runs from Task Manager_ – Steve Oct 02 '21 at 21:10
  • Could you try adding this line at the end of the program: `GC.KeepAlive(lw);`, to see if it makes any difference? – Theodor Zoulias Oct 02 '21 at 21:50
  • Edit OnCreated() and add `throw new Exception("test");` Ensure that you get a good diagnostic for this exception in, say, a log file. – Hans Passant Oct 02 '21 at 22:45
  • I found the difference: it is debug vs. release, debug works. KeepAlive doesn't help, OnCreated is not even called for 2nd directory. – petr.f77 Oct 04 '21 at 09:30
  • The problem is almost certainly related to the `FileSystemWatcher` going out of scope and being garbage collected: [FileSystemWatcher not firing events](https://stackoverflow.com/questions/16278783/filesystemwatcher-not-firing-events). The difference in behavior between Debug and Release is probably accidental, and certainly not something that you should rely on. – Theodor Zoulias Oct 04 '21 at 09:47
  • Are you sure that the list of paths contained in config.txt is the same? Possibly debug version (usually) running from debug folder read a different file? Also, check current folder when running: if not properly set, task scheduler will run the program not from its folder location. Logging current path, read path(es) and the possible exception you are ignoring can be helpful. And yes, forget keepAlive, it's seldom if never the right thing to do. – Gian Paolo Oct 04 '21 at 10:02
  • 1
    Are you sure you're not missing an exception thrown by AppendAllText()? Try placing it inside a lock on a shared static object to force events raised "almost simultaneously" to wait their turn to update the log file. Also give log.txt an explicit path. – AlanK Oct 04 '21 at 10:23

1 Answers1

1

To make sure that the FileSystemWatchers will not get garbage collected before the end of the program, you could do this:

public class FolderWatch
{
    private FileSystemWatcher watcher;

    //...

    public void KeepAlive() => GC.KeepAlive(watcher);
}

...and call the KeepAlive for all FolderWatchs at the end of the Main method:

private static void Main(string[] args)
{
    var lw = new List<FolderWatch>();
    //...
    Thread.Sleep(Timeout.InfiniteTimeSpan);
    lw.ForEach(w => w.KeepAlive());
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thanks. I tried it, but doesn't help. Anyway, after Sleep code won't be executed. – petr.f77 Oct 04 '21 at 15:40
  • 1
    @petr the `KeepAlive` method has no code. You can check the [source code](https://referencesource.microsoft.com/mscorlib/system/gc.cs.html#18b31b2edcc0f711). Its purpose is just to prevent the garbage collector from recycling an object which, in the eyes of the garbage collector, seems to have fallen out of scope. This issue affects components like the `FileSystemWatcher` and the `System.Threading.Timer`. Other components, like the `System.Timers.Timer`, are not affected by this. – Theodor Zoulias Oct 04 '21 at 16:13
  • 1
    You are totally right, I added 'GC.KeepAlive(w_arr);` after `Thread.Sleep(Timeout.InfiniteTimeSpan);` and this did the work. Thank you! – petr.f77 Oct 04 '21 at 16:31
  • Does it really need to be `ForEach`? Would not just `GC.KeepAlive(lw)` do the trick? – Guru Stron Oct 05 '21 at 10:09
  • @GuruStron according to my experiments the `GC.KeepAlive(lw)` is sufficient. But since the OP had reported that this didn't work, I proposed a more hard-core alternative. :-) – Theodor Zoulias Oct 05 '21 at 11:41
  • Btw the way I tested it is by creating a `FileSystemWatcher` inside a method, returning it wrapped in a [`WeakReference`](https://learn.microsoft.com/en-us/dotnet/api/system.weakreference), and checking the `IsAlive` property after calling `GC.Collect`. – Theodor Zoulias Oct 05 '21 at 11:46