Using FileSystemWatcher with Multiple Files

I want to use FileSystemWatcher to monitor a directory and its subdirectories for relocatable files. And then I want to call the code when all the files have been moved. But I do not know how to do this. My code, as it is, will run every time a file is moved, and if the user moves several files at once, I only want it to run once for all files. So basically I want to create a list, and as soon as the movement of all files is complete, I want to make stuff in this list ...

Here is the code:

class Monitor { private List<string> _filePaths; public void CreateWatcher(string path) { FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Filter = "*.*"; watcher.Created += new FileSystemEventHandler(watcher_FileCreated); watcher.Path = path; watcher.IncludeSubdirectories = true; watcher.EnableRaisingEvents = true; } void watcher_FileCreated(object sender, FileSystemEventArgs e) { _filePaths.Add(e.FullPath); Console.WriteLine("Files have been created or moved!"); } } 

UPDATE: Trying to use Chris code, but it doesn't work (see my comment in Chris's answer):

 class Monitor { private List<string> _filePaths; private Timer _notificationTimer; private FileSystemWatcher _fsw; public Monitor(string path) { _notificationTimer = new Timer(); _notificationTimer.Elapsed += notificationTimer_Elapsed; // CooldownSeconds is the number of seconds the Timer is 'extended' each time a file is added. // I found it convenient to put this value in an app config file. int CooldownSeconds = 1; _notificationTimer.Interval = CooldownSeconds * 1000; _fsw = new FileSystemWatcher(); _fsw.Path = path; _fsw.IncludeSubdirectories = true; _fsw.EnableRaisingEvents = true; // Set up the particulars of your FileSystemWatcher. _fsw.Created += fsw_Created; } private void notificationTimer_Elapsed(object sender, ElapsedEventArgs e) { // // Do what you want to do with your List of files. // Console.Write("Done"); // Stop the timer and wait for the next batch of files. _notificationTimer.Stop(); // Clear your file List. _filePaths = new List<string>(); } // Fires when a file is created. private void fsw_Created(object sender, FileSystemEventArgs e) { // Add to our List of files. _filePaths.Add(e.Name); // 'Reset' timer. _notificationTimer.Stop(); _notificationTimer.Start(); } } 

UPDATE 2:

Tried this according to Anders:

 public class FileListEventArgs : EventArgs { public List<string> FileList { get; set; } } public class Monitor { private List<string> filePaths; private ReaderWriterLockSlim rwlock; private Timer processTimer; public event EventHandler FileListCreated; public void OnFileListCreated(FileListEventArgs e) { if (FileListCreated != null) FileListCreated(this, e); } public Monitor(string path) { filePaths = new List<string>(); rwlock = new ReaderWriterLockSlim(); FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Filter = "*.*"; watcher.Created += watcher_FileCreated; watcher.Path = path; watcher.IncludeSubdirectories = true; watcher.EnableRaisingEvents = true; } private void ProcessQueue() { List<string> list = new List<string>(); try { Console.WriteLine("Processing queue, " + filePaths.Count + " files created:"); rwlock.EnterReadLock(); } finally { if (processTimer != null) { processTimer.Stop(); processTimer.Dispose(); processTimer = null; OnFileListCreated(new FileListEventArgs { FileList = filePaths }); filePaths.Clear(); } rwlock.ExitReadLock(); } } void watcher_FileCreated(object sender, FileSystemEventArgs e) { try { rwlock.EnterWriteLock(); filePaths.Add(e.FullPath); if (processTimer == null) { // First file, start timer. processTimer = new Timer(2000); processTimer.Elapsed += (o, ee) => ProcessQueue(); processTimer.Start(); } else { // Subsequent file, reset timer. processTimer.Stop(); processTimer.Start(); } } finally { rwlock.ExitWriteLock(); } } 

I had to move the event trigger to a finally expression, and this works. I do not know if there is a reason why I do not want to do this?

+6
source share
4 answers

As Jay says: a timer is probably the only way to β€œgroup” events. Locking can be excessive, but I don't like the idea of ​​mutating a collection in a multi-threaded situation (I think that events from fswatcher are called in threads from the pool).

  public class Monitor : IDisposable { private List<string> filePaths; private ReaderWriterLockSlim rwlock; private Timer processTimer; private string watchedPath; private FileSystemWatcher watcher; public Monitor(string watchedPath) { filePaths = new List<string>(); rwlock = new ReaderWriterLockSlim(); this.watchedPath = watchedPath; InitFileSystemWatcher(); } private void InitFileSystemWatcher() { watcher = new FileSystemWatcher(); watcher.Filter = "*.*"; watcher.Created += Watcher_FileCreated; watcher.Error += Watcher_Error; watcher.Path = watchedPath; watcher.IncludeSubdirectories = true; watcher.EnableRaisingEvents = true; } private void Watcher_Error(object sender, ErrorEventArgs e) { // Watcher crashed. Re-init. InitFileSystemWatcher(); } private void Watcher_FileCreated(object sender, FileSystemEventArgs e) { try { rwlock.EnterWriteLock(); filePaths.Add(e.FullPath); if (processTimer == null) { // First file, start timer. processTimer = new Timer(2000); processTimer.Elapsed += ProcessQueue; processTimer.Start(); } else { // Subsequent file, reset timer. processTimer.Stop(); processTimer.Start(); } } finally { rwlock.ExitWriteLock(); } } private void ProcessQueue(object sender, ElapsedEventArgs args) { try { Console.WriteLine("Processing queue, " + filePaths.Count + " files created:"); rwlock.EnterReadLock(); foreach (string filePath in filePaths) { Console.WriteLine(filePath); } filePaths.Clear(); } finally { if (processTimer != null) { processTimer.Stop(); processTimer.Dispose(); processTimer = null; } rwlock.ExitReadLock(); } } protected virtual void Dispose(bool disposing) { if (disposing) { if (rwlock != null) { rwlock.Dispose(); rwlock = null; } if (watcher != null) { watcher.EnableRaisingEvents = false; watcher.Dispose(); watcher = null; } } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } 

Do not forget to set the buffer size on your fswatcher AND implement the "resurrection" of fswatcher if it receives an error (ie, attaches an error event to the method that recreates the observer).

Edit: note that in this example, the timer is System.Timers.Timer, not System.Threading.Timer

Edit: now contains error handling for the observer, manage the logic.

+9
source

I had to do the same. Use System.Timers.Timer in your Monitor class and program its past event to process the file list and clear the list. When the first item is added to the file list through FSW events, start the timer. When subsequent items are added to the timer "reset" list, stop and restart it.

Something like that:

 class Monitor { FileSystemWatcher _fsw; Timer _notificationTimer; List<string> _filePaths = new List<string>(); public Monitor() { _notificationTimer = new Timer(); _notificationTimer.Elapsed += notificationTimer_Elapsed; // CooldownSeconds is the number of seconds the Timer is 'extended' each time a file is added. // I found it convenient to put this value in an app config file. _notificationTimer.Interval = CooldownSeconds * 1000; _fsw = new FileSystemWatcher(); // Set up the particulars of your FileSystemWatcher. _fsw.Created += fsw_Created; } private void notificationTimer_Elapsed(object sender, ElapsedEventArgs e) { // // Do what you want to do with your List of files. // // Stop the timer and wait for the next batch of files. _notificationTimer.Stop(); // Clear your file List. _filePaths = new List<string>(); } // Fires when a file is created. private void fsw_Created(object sender, FileSystemEventArgs e) { // Add to our List of files. _filePaths.Add(e.Name); // 'Reset' timer. _notificationTimer.Stop(); _notificationTimer.Start(); } } 
+4
source

Rx - a throttle - facilitates this work. This is a test, reusable solution.

 public class Files { public static FileSystemWatcher WatchForChanges(string path, string filter, Action triggeredAction) { var monitor = new FileSystemWatcher(path, filter); //monitor.NotifyFilter = NotifyFilters.FileName; monitor.Changed += (o, e) => triggeredAction.Invoke(); monitor.Created += (o, e) => triggeredAction.Invoke(); monitor.Renamed += (o, e) => triggeredAction.Invoke(); monitor.EnableRaisingEvents = true; return monitor; } } 

Allows you to combine events in one sequence

  public IObservable<Unit> OnUpdate(string path, string pattern) { return Observable.Create<Unit>(o => { var watcher = Files.WatchForChanges(path, pattern, () => o.OnNext(new Unit())); return watcher; }); } 

then finally use

 OnUpdate(path, "*.*").Throttle(Timespan.FromSeconds(10)).Subscribe(this, _ => DoWork()) 

u obviously can play with throttle speed according to your needs.

+3
source

Also, set the buffer size larger than the default to avoid buffer overflow. This happens when more than 25 files are deleted in the source directory (in my test). If 200 files are dropped, the event handler is called for only a few files, but not for all.

_watcher.InternalBufferSize = 65536; // Maximum buffer size

0
source

Source: https://habr.com/ru/post/894298/


All Articles