FileSystemWatcher: Events werden mehrfach ausgelöst – Lösungsansätze

Die Klasse FileSystemWatcher ist Teil des .NET Frameworks und ermöglicht die einfache Überwachung des Dateisystems. Somit ist es möglich auf Ereignisse wie dem Anlegen oder Verändern von Dateien und Verzeichnissen zu reagieren.
Ein bekanntes Problem des FileSystemWatcher ist, dass Events u.U. mehrfach ausgelöst werden. Diesem Problem soll hier auf den Grund gegangen werden und einige Lösungsansätze gezeigt werden.

Alle Code-Beispiele sind dabei in C# aufgeführt, allerdings ist die Vorgehensweise auch auf andere .NET Sprachen übertragbar. Die Visual Studio Solution mit allen Code-Beispielen steht weiter unten zum Download bereit.

Das Problem

Um das Problem zu demonstrieren, legen wir uns zunächst ein neues Projekt als Konsolenapplikation im Visual Studio an und fügen folgenden Code ein (Projekt 01_FileSystemWatcherMultipleEvents in der Beispiel-Solution):

class Program
{
	private static FileSystemWatcher fsw;
	private static string path = @"C:\Test";

	static void Main(string[] args)
	{
		fsw = new FileSystemWatcher(path);
		fsw.Changed += fsw_Changed;
		fsw.EnableRaisingEvents = true;

		Console.ReadKey();
	}

	static void fsw_Changed(object sender, FileSystemEventArgs e)
	{
		var file = e.Name;
		var changeType = e.ChangeType;
		Console.WriteLine("File '" + file + "' was changed (ChangeType: " + changeType + ")");
	}
}

Hier wird einfach ein FileSystemWatcher angelegt, der das Verzeichnis C:\Test überwacht und ein Event auslösen soll, wenn in diesem Verzeichnis Dateien geändert werden. Durch Setzen der Eigenschaft EnableRaisingEvents auf true wird die Überwachung dann gestartet. Mehr wird an dieser Stelle nicht gemacht, auch wenn die Klasse FileSystemWatcher noch einige Eigenschaften bietet, die Überwachung genauer zu spezifizieren. Man könnte auch weitere Eventhandler registrieren, die ausgelöst werden, wenn Dateien/Verzeichnisse angelegt, umbenannt oder gelsöcht werden, aber wir konzentrieren uns hier lediglich auf das Changed-Event.

Nachdem das Programm gestartet wurde, legen wir im zu überwachenden Verzeichnis eine neue Text-Datei (test.txt) an. Hier wird der FileSystemWatcher noch nicht aktiv, das wir wie eben angesprochen nur auf Änderungen an Dateien/Verzeichnissen benachrichtigt werden wollen.
Wenn wir diese Datei nun allerdings mit dem Editor (Notepad) öffnen, ein beliebiges Wort schreiben und anschließend speichern, erhalten wir folgendes Ergebnis auf der Konsole:

File 'test.txt' was changed (ChangeType: Changed) 
File 'test.txt' was changed (ChangeType: Changed)

Das Ereignis wird folglich zwei mal ausgelöst, obwohl die Datei nur einmal gespeichert wurde.

Ursachen

Die Ursache liegt nun allerdings nicht am FileSystemWatcher, sondern an den Vorgängen, die beim Speichern der Text-Datei stattfinden: Hier wird die Datei beim Speichern offensichtlich zwei mal verändert: Zuerst wird die veränderte Datei auf die Festplatte geschrieben und anschließend werden die Datei-Attribute gesetzt, was technisch auch ein Änderungs-Vorgang an der Datei darstellt. Der FileSystemWatcher löst das Changed-Event folgerichtig auch zwei mal aus.

Entgegen der häufig vertretenen Meinung, dass es sich dabei um einen Bug des FileSystemWatchers handelt, liegt das Problem hier eher an dem Programm, welches zum Speichern der Datei verwendet wird (Notepad). Auch kann es beim Einsatz von anderen Programmen passieren, dass bei einer einzelnen Operation noch sehr viel mehr Veränderungen an einer Datei vorgenommen werden und der FileSystemWatcher folglich für jede einzelne Veränderung der Datei auch das Changed-Event auslöst.

Einfacher Lösungsansatz (Pausieren der Überwachung)

Oftmals ist es allerdings nicht erforderlich, auf jede einzelne Änderung einer Datei zu reagieren, wenn diese durch eine einzelne (Programm-)Operation ausgelöst werden. Hier sollte die Änderung zwar registiert werden, aber viele Veränderung in einem zeitlich sehr kurzen Abstand sollten als eine einzige Änderung betrachtet werden.

Um das Problem nun auf einfache Art und Weise in den Griff zu bekommen, muss der Changed-Eventhandler nun um wenig Code erweitert werden (Project 02_PauseMonitoring in der Beispiel-Solution):

static void fsw_Changed(object sender, FileSystemEventArgs e)
{
	try
	{
		fsw.EnableRaisingEvents = false;
		var file = e.Name;
		var changeType = e.ChangeType;
		Console.WriteLine("File '" + file + "' was changed (ChangeType: " + changeType + ")");
	}
	finally
	{
		fsw.EnableRaisingEvents = true;
	}
}

Wenn der Eventhandler nun bei der ersten Datei-Änderung aufgerufen wird, wird die Überwachung des Verzeichnisses deaktiviert, indem die Eigenschaft EnableRaisingEvents des FileSystemWatchers auf false gesetzt wird. Nachdem der Eventhandler durchlaufen wurde, wird die Überwachung des Verzeichnisses wieder aktiviert. Dies geschieht zur Sicherheit in einem finally Block, damit sichergestellt wird, dass diese Operation in jedem Fall ausgeführt wird.

Wird das Programm nun ein weiteres Mal ausgeführt und die Datei verändert und gespeichert, wird das Event erwartungsgemäß nur einmal ausgelöst:

File 'test.txt' was changed (ChangeType: Changed)

Für einfache Programme kann diese Lösung bereits ausreichend sein. Jedoch kann es durch die Aussetzung der Verzeichnis-Überwachung dazu kommen, dass Änderungen (z.B. an weiteren Dateien) nicht registirert werden. Dieses Problem wird umso deutlicher, wenn der Programmcode des Changed-Eventhandlers komplexer ist und längere Zeit beansprucht.

Um dies zu verdeutlichen kann man eine künstliche Verzögerung (Thread.Sleep) im Eventhandler einfügen:

static void fsw_Changed(object sender, FileSystemEventArgs e)
{
	try
	{
		fsw.EnableRaisingEvents = false;
		var file = e.Name;
		var changeType = e.ChangeType;
		Console.WriteLine("File '" + file + "' was changed (ChangeType: " + changeType + ")");
		Thread.Sleep(TimeSpan.FromSeconds(5));
	}
	finally
	{
		fsw.EnableRaisingEvents = true;
	}
}

Wenn das Programm nun ein weiteres Mal ausgeführt wird und die Datei test.txt zwei Mal in kurzer Zeit geändert wird, nämlich innerhalb der fünf Sekunden, in den der Thread pausiert wurde, wird die zweite Änderung nicht registriert.
Dieses Verhalten dürfte in den meisten Fällen ebenso unerwünscht sein, daher trägt diese einfache Lösung wirklich nur für einfache Programme

Besserer Lösungsansatz (Puffern des Changed-Eventhandlers)

Ein besserer Lösungsansatz wäre, wenn die Überwachung des Verzeichnisses nicht ausgesetzt werden müsste, sondern im Eventhandler des Changed-Ereignisses eine Logik implementiert wäre, die verhindert, dass der Eventhandler in kurzer Zeit mehrfach ausgeführt wird. Das Project 03_BufferedEventHandler beinhaltet den dazu gehörigen Code:

private static FileSystemWatcher fsw;
private static string path = @"C:\Test";
private static double bufferDelay = 500;
private static IList<string> changedList;

static void Main(string[] args)
{
	changedList = new List<string>();
	fsw = new FileSystemWatcher(path);
	fsw.Changed += fsw_Changed;
	fsw.EnableRaisingEvents = true;

	Console.ReadKey();
}

static void fsw_Changed(object sender, FileSystemEventArgs e)
{
	string fullPath = e.FullPath;

	lock (changedList)
	{
		if (changedList.Contains(fullPath))
			return;
		else
			changedList.Add(fullPath);
	}

	var timer = new Timer(bufferDelay) { AutoReset = false };

	timer.Elapsed += (object elapsedSender, ElapsedEventArgs elapsedArgs) =>
	{
		// Add your logic here.
		var file = e.Name;
		var changeType = e.ChangeType;
		Console.WriteLine("File '" + file + "' was changed (ChangeType: " + changeType + ")");

		lock (changedList)
		{
			changedList.Remove(fullPath);
		}
	};

	timer.Start();
}

In diesem Beispiel wurde zunächst eine einfache Liste (changedList) hinzugefügt. Im Eventhandler wird dann die gerade veränderte Datei dieser Liste angefügt, falls diese noch nicht in der Liste enthalten ist. Falls diese bereits Teil der Liste ist, wird die Ausführung der Methode durch ein return beendet. Ansonsten wird ein Timer gestartet, der nach 500ms zunächst die eigentliche Logik des Eventhandlers ausführt und anschließend die Datei wieder aus der changedList entfernt.

Auf diese Weise werden alle Veränderungen an einer Datei gepuffert, die innerhalb von einer halben Sekunde stattfinden. Erst nach Ablaufen des Timers wird dann die eigentliche Logik des Eventhandlers ausgeführt.

Nun kann über die Variable bufferDelay gesteuert werden, wie lange auf Folge-Events einer Datei-Veränderung gewartet werden soll. Im Beispiel wird diese Variable mit einer halben Sekunde definiert. Folglich werden auch alle überflüssigen Changed-Events, die innerhalb dieser Zeit die gleiche Datei betreffen „verschluckt“.
In der Praxis kann es hier notwendig sein, die Zeitspanne der Verzögerung anzupassen, um keine wichtigen Events zu übersehen. Hier muss man abschätzen, in welchen minimalen Zeitabständen Veränderungen an der gleichen Datei realisitsch sind.

BufferedFileSystemWatcher

Um nun eine wiederverwendbare Lösung zu schaffen, kann der hier vorgestellte Ansatz in eine eigene Klasse ausgelagert werden. Ebenso kann diese Logik auch für die anderen Ereignisse des FileSystemWatchers angewendet werden (Created, Deleted und Renamed). Die Implementierung ist im Project 04_BufferedFileSystemWatcher in der Beispiel-Solution zu finden.

Hier wird eine Klasse BufferedFileSystemWatcher definiert, welche sich von FileSystemWatcher ableitet:

/// <summary>
/// Listens to the file system change notifications and raises events when a directory, or file in a directory, changes.
/// The events are not raised immediately, but are buffered so that events which are raised twice by the included FileSystemWatcher are only raised once.
/// </summary>
public class BufferedFileSystemWatcher : FileSystemWatcher
{
	private IList<string> changedList;
	private IList<string> createdList;
	private IList<string> deletedList;
	private IList<string> renamedList;

	private const string StandardFilter = "*.*"; // Standard filter is all files.
	private const double StandardBufferDelay = 500; // Standard buffer delay of 500ms.

	/// <summary>
	/// Occurs when a file or directory in the specified Path is changed.
	/// </summary>
	public new event FileSystemEventHandler Changed;

	/// <summary>
	/// Occurs when a file or directory in the specified Path is created.
	/// </summary>
	public new event FileSystemEventHandler Created;

	/// <summary>
	/// Occurs when a file or directory in the specified Path is deleted.
	/// </summary>
	public new event FileSystemEventHandler Deleted;

	/// <summary>
	/// Occurs when a file or directory in the specified Path is renamed.
	/// </summary>
	public new event FileSystemEventHandler Renamed;

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class.
	/// </summary>
	public BufferedFileSystemWatcher()
		: this(string.Empty, StandardFilter, StandardBufferDelay, BufferedChangeTypes.All)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class.
	/// </summary>
	/// <param name="bufferedChangeTypes">The BufferedChangeTypes specifying which events should be buffered.</param>
	public BufferedFileSystemWatcher(BufferedChangeTypes bufferedChangeTypes)
		: this(string.Empty, StandardFilter, StandardBufferDelay, bufferedChangeTypes)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specifed buffer delay time.
	/// </summary>
	/// <param name="bufferDelay">The time in milliseconds the raised events should be buffered.</param>
	public BufferedFileSystemWatcher(long bufferDelay)
		: this(string.Empty, StandardFilter, bufferDelay, BufferedChangeTypes.All)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specifed buffer delay time.
	/// </summary>
	/// <param name="bufferDelay">The time in milliseconds the raised events should be buffered.</param>
	/// <param name="bufferedChangeTypes">The BufferedChangeTypes specifying which events should be buffered.</param>
	public BufferedFileSystemWatcher(long bufferDelay, BufferedChangeTypes bufferedChangeTypes)
		: this(string.Empty, StandardFilter, bufferDelay, bufferedChangeTypes)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory to monitor.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	public BufferedFileSystemWatcher(string path)
		: this(path, StandardFilter, StandardBufferDelay, BufferedChangeTypes.All)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory to monitor.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	/// <param name="bufferedChangeTypes">The BufferedChangeTypes specifying which events should be buffered.</param>
	public BufferedFileSystemWatcher(string path, BufferedChangeTypes bufferedChangeTypes)
		: this(path, StandardFilter, StandardBufferDelay, bufferedChangeTypes)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory to monitor and buffer delay time.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	/// <param name="bufferDelay">The time in milliseconds the raised events should be buffered.</param>
	public BufferedFileSystemWatcher(string path, long bufferDelay)
		: this(path, StandardFilter, bufferDelay, BufferedChangeTypes.All)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory and type of files to monitor.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	/// <param name="filter">The type of files to watch. For example, "*.txt" watches for changes to all text files.</param>
	public BufferedFileSystemWatcher(string path, string filter)
		: this(path, filter, StandardBufferDelay, BufferedChangeTypes.All)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory and type of files to monitor.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	/// <param name="filter">The type of files to watch. For example, "*.txt" watches for changes to all text files.</param>
	/// <param name="bufferedChangeTypes">The BufferedChangeTypes specifying which events should be buffered.</param>
	public BufferedFileSystemWatcher(string path, string filter, BufferedChangeTypes bufferedChangeTypes)
		: this(path, filter, StandardBufferDelay, bufferedChangeTypes)
	{
	}

	/// <summary>
	/// Initializes a new instance of the BufferedFileSystemWatcher class, given the specified directory, type of files to monitor, buffer delay time and the types of events which should be handled delayed.
	/// </summary>
	/// <param name="path">The directory to monitor, in standard or Universal Naming Convention (UNC) notation.</param>
	/// <param name="filter">The type of files to watch. For example, "*.txt" watches for changes to all text files.</param>
	/// <param name="bufferDelay">The time in milliseconds the raised events should be buffered.</param>
	/// <param name="bufferedChangeTypes">The BufferedChangeTypes specifying which events should be buffered.</param>
	public BufferedFileSystemWatcher(string path, string filter, double bufferDelay, BufferedChangeTypes bufferedChangeTypes)
		: base(path, filter)
	{
		this.changedList = new List<string>();
		this.createdList = new List<string>();
		this.deletedList = new List<string>();
		this.renamedList = new List<string>();

		this.Path = path;
		this.Filter = filter;
		this.BufferDelay = bufferDelay;
		this.BufferedChangeTypes = BufferedChangeTypes.All;

		base.Changed += BufferedFileSystemWatcher_Changed;
		base.Created += BufferedFileSystemWatcher_Created;
		base.Deleted += BufferedFileSystemWatcher_Deleted;
		base.Renamed += BufferedFileSystemWatcher_Renamed;
	}

	/// <summary>
	/// Gets or sets the buffer delay time in milliseconds, i.e. the time in which the events raised should be buffered.
	/// </summary>
	public double BufferDelay
	{
		get;
		set;
	}

	/// <summary>
	/// Gets or sets the BufferedChangeTypes, i.e. the types of events which should be buffered by the BufferedFileSystemWatcher.
	/// If this property is set to DelayedChangeTypes.None, the BufferedFileSystemWatcher will behave like a normal FileSystemWatcher.
	/// </summary>
	public BufferedChangeTypes BufferedChangeTypes
	{
		get;
		set;
	}

	/// <summary>
	/// Invoked whenever a file or directory in the specified Path is changed (buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual void OnBufferedChanged(FileSystemEventArgs e)
	{
		string fullPath = e.FullPath;

		lock (this.changedList)
		{
			if (this.changedList.Contains(fullPath))
				return;
			else
				this.changedList.Add(fullPath);
		}

		var timer = new Timer(this.BufferDelay) { AutoReset = false };

		timer.Elapsed += (object elapsedSender, ElapsedEventArgs elapsedArgs) =>
		{
			FileSystemEventHandler tmp = this.Changed;

			if (tmp != null)
				tmp(this, e);

			lock (this.changedList)
			{
				this.changedList.Remove(fullPath);
			}
		};

		timer.Start();
	}

	/// <summary>
	///  Invoked whenever a file or directory in the specified Path is changed (not buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual new void OnChanged(FileSystemEventArgs e)
	{
		FileSystemEventHandler tmp = this.Changed;

		if (tmp != null)
			tmp(this, e);
	}

	/// <summary>
	/// Inviked whenever a file or directory in the specified Path is created (buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual void OnBufferedCreated(FileSystemEventArgs e)
	{
		string fullPath = e.FullPath;

		lock (this.createdList)
		{
			if (this.createdList.Contains(fullPath))
				return;
			else
				this.createdList.Add(fullPath);
		}

		var timer = new Timer(this.BufferDelay) { AutoReset = false };

		timer.Elapsed += (object elapsedSender, ElapsedEventArgs elapsedArgs) =>
		{
			FileSystemEventHandler tmp = this.Created;

			if (tmp != null)
				tmp(this, e);

			lock (this.createdList)
			{
				this.createdList.Remove(fullPath);
			}
		};

		timer.Start();
	}

	 /// <summary>
	/// Inviked whenever a file or directory in the specified Path is created (not buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual new void OnCreated(FileSystemEventArgs e)
	{
		FileSystemEventHandler tmp = this.Created;

		if (tmp != null)
			tmp(this, e);
	}

	/// <summary>
	/// Invoked whenever a file or directory in the specified Path is deleted (buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual void OnBufferedDeleted(FileSystemEventArgs e)
	{
		string fullPath = e.FullPath;

		lock (this.deletedList)
		{
			if (this.deletedList.Contains(fullPath))
				return;
			else
				this.deletedList.Add(fullPath);
		}

		var timer = new Timer(this.BufferDelay) { AutoReset = false };

		timer.Elapsed += (object elapsedSender, ElapsedEventArgs elapsedArgs) =>
		{
			FileSystemEventHandler tmp = this.Deleted;

			if (tmp != null)
				tmp(this, e);

			lock (this.deletedList)
			{
				this.deletedList.Remove(fullPath);
			}
		};

		timer.Start();
	}

	/// <summary>
	/// Invoked whenever a file or directory in the specified Path is deleted (not buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual new void OnDeleted(FileSystemEventArgs e)
	{
		FileSystemEventHandler tmp = this.Deleted;

		if (tmp != null)
			tmp(this, e);
	}

	/// <summary>
	/// Invkoed whenever a file or directory in the specified Path is renamed (buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual void OnBufferedRenamed(RenamedEventArgs e)
	{
		string fullPath = e.FullPath;

		lock (this.renamedList)
		{
			if (this.renamedList.Contains(fullPath))
				return;
			else
				this.renamedList.Add(fullPath);
		}

		var timer = new Timer(this.BufferDelay) { AutoReset = false };

		timer.Elapsed += (object elapsedSender, ElapsedEventArgs elapsedArgs) =>
		{
			FileSystemEventHandler tmp = this.Renamed;

			if (tmp != null)
				tmp(this, e);

			lock (this.renamedList)
			{
				this.renamedList.Remove(fullPath);
			}
		};

		timer.Start();
	}

	/// <summary>
	/// Invkoed whenever a file or directory in the specified Path is renamed (not buffered).
	/// </summary>
	/// <param name="e">The FileSystemEventArgs that contains the event data.</param>
	protected virtual new void OnRenamed(RenamedEventArgs e)
	{
		FileSystemEventHandler tmp = this.Renamed;

		if (tmp != null)
			tmp(this, e);
	}

	private void BufferedFileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
	{
		if ((this.BufferedChangeTypes & BufferedChangeTypes.Changed) == BufferedChangeTypes.Changed)
			OnBufferedChanged(e);
		else
			OnChanged(e);
	}

	private void BufferedFileSystemWatcher_Created(object sender, FileSystemEventArgs e)
	{
		if ((this.BufferedChangeTypes & BufferedChangeTypes.Created) == BufferedChangeTypes.Created)
			OnBufferedCreated(e);
		else
			OnCreated(e);
	}

	private void BufferedFileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
	{
		if ((this.BufferedChangeTypes & BufferedChangeTypes.Deleted) == BufferedChangeTypes.Deleted)
			OnBufferedDeleted(e);
		else
			OnDeleted(e);
	}

	private void BufferedFileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
	{
		if ((this.BufferedChangeTypes & BufferedChangeTypes.Renamed) == BufferedChangeTypes.Renamed)
			OnBufferedRenamed(e);
		else
			OnRenamed(e);
	}
}

/// <summary>
/// Enum defining the change types which should be buffered by the BufferedFileSystemWatcher.
/// </summary>
[Flags]
public enum BufferedChangeTypes
{
	/// <summary>
	/// No events should be buffered.
	/// The BufferedFileSystemWatcher will behave like a normal FileSystemWatcher.
	/// </summary>
	None = 0,
	/// <summary>
	/// The changed events should be buffered.
	/// </summary>
	Changed = 1,
	/// <summary>
	/// The created events should be buffered.
	/// </summary>
	Created = 2,
	/// <summary>
	/// The deleted events should be buffered.
	/// </summary>
	Deleted = 4,
	/// <summary>
	/// The renamed events should be buffered.
	/// </summary>
	Renamed = 8,
	/// <summary>
	/// All event types should be buffered.
	/// These are the changed, created, deleted and renamed events.
	/// </summary>
	All = 15
}

Die Vorgehensweise ist ja schon aus dem vorherigen Lösungsansatz bekannt. Damit nicht jedes Event gepuffert wird, wurde noch ein Enum (BufferedChangeTypes) hinzugefügt, mit dem genau spezifiziert kann, welche Events gepuffert werden sollen und welche nicht. Wird hier BufferedChangeTypes.None angegeben, verhält sich der BufferedFileSystemWatcher genau so wie ein FileSystemWatcher.

Die Verwendung des BufferedFileSystemWatchers erfolgt dann analog zum FileSystemWatcher:

private static BufferedFileSystemWatcher bfsw;
private static string path = @"C:\Test";

static void Main(string[] args)
{
	bfsw = new BufferedFileSystemWatcher(path);
	bfsw.BufferedChangeTypes = BufferedChangeTypes.Changed;
	bfsw.Changed += bfsw_Changed;
	bfsw.EnableRaisingEvents = true;

	Console.ReadKey();
}

static void bfsw_Changed(object sender, FileSystemEventArgs e)
{
	var file = e.Name;
	var changeType = e.ChangeType;
	Console.WriteLine("File '" + file + "' was changed (ChangeType: " + changeType + ")");
}

Bei der Ausführung dieses Codes wird beim Speichern der Textdatei mit dem Notepad das Changed-Ereignis nur einmal ausgelöst.

Zu beachten ist hier jedoch wieder die Zeitspanne, in der Events gepuffert werden (Property BufferDelay). Hier gilt es wieder einen praxistauglichen Wert zu ermitteln, damit keine Änderungen an Dateien/Verzeichnissen „übersehen“ werden.

Download

Hier gibt es die komplette Visual Studio Solution mit dem kompletten Code dieses Beitrags zum Download:

Download FileSystemWatcherSamples.zip

Links

2 Kommentare zu „FileSystemWatcher: Events werden mehrfach ausgelöst – Lösungsansätze“

  1. Das ist genau mein Problem.
    Ich soll ein Ordner überwachen, wenn ein Datei im dieser Ordner gespeichert wird, läuft ein Prozez die eigene Zeit braucht, ich soll auch das Systemwatcher „pausieren“ (weil in diese Moment könnte noch ein andere Datei gepeichert werden.) dann wenn das Prozez fertig ist, das fsw. wieder starten..

    hast du ein Beispiel dafür?

    1. Hallo Cesar,

      ich würde sagen, das kommt ganz darauf an, welche Änderungen an Dateien du mit dem FileSystemWatcher mitbekommen willst.
      Wenn es dir nur um diese eine Datei geht (also unabhängig davon, ob weitere Dateien geschrieben werden), dann kannst du natürlich im Event-Handler des FileSystemWatchers diesen zunächst pausieren, anschließend den Verarbeitungs-Prozess starten und dann den FileSystemWatcher wieder aktiv schalten.

      Falls du allerdings weitere Änderungen an (anderen) Dateien mitbekommen und evtl. darauf reagieren musst, solltest du den FileSystemWatcher nicht pausieren. In diesem Fall muss die Art der Datei-Änderung im Event-Handler des FileSystemwatchers genauer untersucht werden und dementsprechend reagiert werden.

      Evtl. ist es auch sinnvoll, den Verarbeitungs-Prozess in einen eigenen Thread auszulagern, damit der Thread „entlastet“ wird, in dem der Event-Handler des FileSystemWatchers läuft. Dann musst du dich allerdings auch um eine evtl. notwendige Thread-Synchonisierung kümmern.

      Oder du probierst es erst einmal mit einem gepufferten FileSystemWatcher, wie im Artikel gezeigt.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert