watcher example .net vb.net filesystemwatcher

example - VB.NET FileSystemWatcher Eventos de cambio mĂșltiple



filesystemwatcher example c# (14)

Tengo el siguiente código:

Imports System.IO Public Class Blah Public Sub New() InitializeComponent() Dim watcher As New FileSystemWatcher("C:/") watcher.EnableRaisingEvents = True AddHandler watcher.Changed, AddressOf watcher_Changed End Sub Private Sub watcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs) MsgBox(e.FullPath) End Sub End Class

Cuando lo ejecuto y guardo los cambios en un archivo en mi unidad C, el código funciona muy bien, excepto que ejecuta el método watcher_Changed () cuatro veces. ¿Alguna idea de por qué? El changeType es "4" cada vez.

Gracias.


Asumiendo que la ruta sea la misma siempre, ¿es posible que el programa que está utilizando para guardar el archivo esté haciendo el guardado en pedazos? ¿O tienes más de un Blah instanciado?

Editar: ¿Tiene algún software antivirus de autoprotección ejecutándose? Esos podrían estar tocando el archivo en el proceso.

De la documentación de MSDN :

Las operaciones comunes del sistema de archivos pueden generar más de un evento. Por ejemplo, cuando se mueve un archivo de un directorio a otro, se pueden generar varios eventos OnChanged y algunos eventos OnCreated y OnDeleted. Mover un archivo es una operación compleja que consiste en múltiples operaciones simples, por lo tanto, generar múltiples eventos. Del mismo modo, algunas aplicaciones (por ejemplo, software antivirus) pueden causar eventos adicionales del sistema de archivos que FileSystemWatcher detecta.

Editar: O tal vez hay algo que ver con cómo Windows está guardando el archivo. Es posible que reciba más de un evento por diferentes cambios. (Uno para el tamaño, uno para la última marca de tiempo de escritura, uno para la última marca de tiempo de acceso y otro para ... otra cosa). Intente configurar la propiedad NotifyFilter FileSystemWatcher en un único tipo de cambio y vea si continúa. para obtener múltiples eventos


Hace un tiempo, tuve la experiencia del mismo problema.

Después de buscar en la web, parecía que no era el único que tenía este problema. :) Entonces, tal vez es un defecto en el FileSystemWatcher ...

Lo he solucionado haciendo un seguimiento de la última vez que se generó el controlador de eventos. Si se ha planteado menos de hace xxx msec, regreso de mi controlador de eventos. Si alguien conoce una solución que es más elegante; Por favor, hágamelo saber. :)

Así es como he trabajado alrededor:

if( e.ChangeType == WatcherChangeTypes.Changed ) { // There is a nasty bug in the FileSystemWatch which causes the // events of the FileSystemWatcher to be called twice. // There are a lot of resources about this to be found on the Internet, // but there are no real solutions. // Therefore, this workaround is necessary: // If the last time that the event has been raised is only a few msec away, // we ignore it. if( DateTime.Now.Subtract (_lastTimeFileWatcherEventRaised).TotalMilliseconds < 500 ) { return; } _lastTimeFileWatcherEventRaised = DateTime.Now; .. handle event


Hay otra posibilidad, que estás cometiendo un error :) Tal vez seas instanciado y finalices tu clase "Blah" antes de usarlo para fines de vigilancia de archivos, y olvidando implementar RemoveHandler por Dispose / o cualquier método de desmontaje relacionado. (?)



Encontré esta página por el mismo problema. Y por lo que parece, incluso si agrega lógica para procesar múltiples eventos de forma condicional, cualquier código que se supone que se procesará se interrumpirá / anulará cuando ocurra un evento posterior (duplicado) y cause un comportamiento no deseado. Creo que una forma de evitar esto sería implementar un manejador de eventos en un hilo diferente de alguna manera ... espero que tenga sentido.

Aclamaciones,

Nico


Mi solución a este problema es un poco como Erics, excepto que utilizo System.Windows.Forms.Timer en lugar de comenzar un nuevo hilo. La idea es que administre el evento de cambio solo cuando haya pasado x ms sin que se hayan modificado los eventos de ningún archivo. Tenga en cuenta que todo se lleva a cabo en el hilo de la GUI para que no haya problemas de subprocesos. Yo uso x = 100.

private Dictionary<String, FileSystemEventArgs> xmlFileChangedEvents = new Dictionary<string, FileSystemEventArgs>(); private void debugXmlWatcher_Changed(object sender, FileSystemEventArgs e) { if (!xmlFileChangedEvents.ContainsKey(e.Name)) xmlFileChangedEvents.Add(e.Name, e); xmlChangeTimer.Stop();//Reset the Forms.Timer so that it times out in 100 ms xmlChangeTimer.Start(); } private void xmlChangeTimer_Tick(object sender, EventArgs e) { foreach (FileSystemEventArgs eventArg in xmlFileChangedEvents.Values) { // //Handle the file changed event here // } xmlFileChangedEvents.Clear(); }


Hice una clase simple que funciona bien para mí. Puede ser útil para otra persona.

using System; using System.IO; using System.Timers; namespace Demo { class FileWatcher { private FileSystemWatcher watcher = new FileSystemWatcher(); private Timer t = new Timer(); public event EventHandler FileChanged; public FileWatcher() { t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed); t.Interval = 1000; } public void Start(String path) { watcher.Path = Path.GetDirectoryName(path); watcher.Filter = Path.GetFileName(path); watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime; watcher.EnableRaisingEvents = true; watcher.Changed += new FileSystemEventHandler(watcher_Changed); } void watcher_Changed(object sender, FileSystemEventArgs e) { if (!t.Enabled) t.Start(); } void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { t.Stop(); if (FileChanged != null) FileChanged(this, null); } } }

Se puede usar así:

FileWatcher FileWatcher1 = new FileWatcher(); FileWatcher1.FileChanged += new EventHandler(FileWatcher1_FileChanged); FileWatcher1.Start("c:/test.txt");


Esta ha sido una peculiaridad enloquecedora de FindFirstChangeNotification () Win32 API desde el día 1 (desde Windows 3.x), y parece que FileSystemWatcher simplemente ajusta esa API. El enfoque del temporizador (presentado anteriormente) es la solución común.

Normalmente creo una clase que envuelve FileSystemWatcher y realiza el filtrado de llamadas de cambio múltiple. Un poco de trabajo extra por escribir, pero vale la pena en reutilización.

public class FileChangeMonitor { private FileSystemWatcher _fsw; DateTime _lastEventTime; public event FileSystemEventHandler Changed; public FileChangeMonitor(string path, string filter) { _fsw = new FileSystemWatcher(path, filter); _fsw.Changed += new FileSystemEventHandler(_fsw_Changed); _fsw.EnableRaisingEvents = true; _fsw.NotifyFilter = NotifyFilters.LastWrite; _fsw.IncludeSubdirectories = false; } private void _fsw_Changed(object sender, FileSystemEventArgs e) { // Fix the FindFirstChangeNotification() double-call bug if (DateTime.Now.Subtract(_lastEventTime).TotalMilliseconds > 100) { _lastEventTime = DateTime.Now; if (this.Changed != null) this.Changed(sender, e); // Bubble the event } } }

A continuación, puede utilizar FileChangeMonitor más o menos como lo haría FileSystemWatcher:

FileChangeMonitor fcm = new FileChangeMonitor(path, filter); fsm.Changed += new FileSystemEventHandler(fsm_Changed); ...

Por supuesto, el código anterior solo maneja el evento Changed y NotifyFilters.LastWrite, pero se entiende la idea.


Truco independiente de la plataforma:

// Class level variable bool m_FileSystemWatcherIsMessy = true; // inside call back if (m_FileSystemWatcherIsMessy) { m_FileSystemWatcherIsMessy = false; return; } else { m_FileSystemWatcherIsMessy = true; }


el controlador de eventos cambiado por el observador activará 3 eventos ... crear, eliminar, cambiar. Solo cuando cambie el nombre de un archivo se disparará el evento onrenamed. Esa es probablemente la razón por la que recibes 4 alertas. Además, la mayoría de los programas ejecutan múltiples operaciones en un archivo antes de cerrarlo. Cada evento se considera un cambio y, por lo tanto, el evento on_changed se activa cada vez.


Si necesita mostrar los eventos de cambio mientras están sucediendo en un formulario, entonces necesita usar el enhebrado. La solución de Eric es la mejor en este sentido, ya que se puede usar fácilmente con o sin una forma que hace que la solución sea más flexible. También maneja los múltiples eventos duplicados muy bien y se asegura de que solo come eventos duplicados solo si es para EL MISMO ARCHIVO. En la solución aceptada, si se cambian dos archivos casi al mismo tiempo, uno de sus eventos podría ignorarse de manera incorrecta.


Desde la sección "Solución de problemas de FileSystemWatcher Components" de la documentación de VS.NET ...

Múltiples eventos creados generados para una sola acción

Puede observar en ciertas situaciones que un solo evento de creación genera múltiples eventos Creado que maneja su componente. Por ejemplo, si usa un componente FileSystemWatcher para supervisar la creación de nuevos archivos en un directorio y luego probarlo con el Bloc de notas para crear un archivo, puede ver dos eventos creados generados aunque solo se haya creado un solo archivo. Esto se debe a que el Bloc de notas realiza varias acciones del sistema de archivos durante el proceso de escritura. El Bloc de notas escribe en el disco en lotes que crean el contenido del archivo y luego los atributos del archivo. Otras aplicaciones pueden funcionar de la misma manera. Como FileSystemWatcher supervisa las actividades del sistema operativo, se recogerán todos los eventos que estas aplicaciones disparen.

Nota: Notepad también puede causar otras generaciones de eventos interesantes. Por ejemplo, si usa ChangeEventFilter para especificar que desea ver solo los cambios de atributos y luego escribe en un archivo en el directorio que está viendo usando el Bloc de notas, generará un evento. Esto es porque el Bloc de notas actualiza el atributo Archivado para el archivo durante esta operación.


Aquí hay una prueba de concepto de cómo manejo esto.

Para probar, crea una nueva aplicación de Windows Forms. En el formulario, agregue un cuadro de texto de líneas múltiples llamado "tbMonitor". Haga clic derecho en el formulario y vaya a Ver código. Reemplace ese código con el código que he incluido a continuación. Tenga en cuenta que configuro el tiempo de espera en un número realmente alto para que pueda jugar un poco con él. En producción, es probable que desee hacer que este número sea mucho más bajo, probablemente alrededor de 10 o 15.

Imports System.IO Imports System.Threading Public Class Form1 Private Const MILLISECONDS_TO_WAIT As Integer = 1000 Private fw As FileSystemWatcher Private Shared AccessEntries As List(Of String) Private Delegate Sub UpdateBoxDelegate(ByVal msg As String) Private Sub UpdateBox(ByVal msg As String) If tbMonitor.InvokeRequired Then Invoke(New UpdateBoxDelegate(AddressOf UpdateBox), New Object() {msg}) Else tbMonitor.AppendText(msg + vbCrLf) End If End Sub Private Sub AccessEntryRemovalTimer(ByVal RawFileName As Object) UpdateBox("Sleeping to watch for " + RawFileName.ToString + " on thread ID " + Thread.CurrentThread.ManagedThreadId.ToString) Thread.Sleep(MILLISECONDS_TO_WAIT) AccessEntries.Remove(RawFileName.ToString) UpdateBox("Removed " + RawFileName.ToString + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString) End Sub Private Sub Changed(ByVal source As Object, ByVal e As FileSystemEventArgs) If AccessEntries.Contains(e.Name) Then UpdateBox("Ignoring a " + e.ChangeType.ToString + " notification for " + e.Name + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString) Return End If Dim AccessTimerThread As Thread AccessEntries.Add(e.Name) UpdateBox("Adding " + e.Name + " to the collection and starting the watch thread.") AccessTimerThread = New Thread(AddressOf AccessEntryRemovalTimer) AccessTimerThread.IsBackground = True AccessTimerThread.Start(e.Name) End Sub Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load tbMonitor.ScrollBars = ScrollBars.Both AccessEntries = New List(Of String) fw = New FileSystemWatcher fw.Path = "C:/temp" fw.NotifyFilter = NotifyFilters.LastWrite Or NotifyFilters.LastAccess Or NotifyFilters.FileName AddHandler fw.Changed, AddressOf Changed AddHandler fw.Created, AddressOf Changed AddHandler fw.Renamed, AddressOf Changed fw.EnableRaisingEvents = True End Sub Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed fw.EnableRaisingEvents = False RemoveHandler fw.Changed, AddressOf Changed RemoveHandler fw.Created, AddressOf Changed RemoveHandler fw.Renamed, AddressOf Changed fw.Dispose() End Sub End Class


La solución de Frederik es por lejos la mejor que he encontrado. Sin embargo, encontré 500 milisegundos para ser demasiado lento. En mi aplicación, un usuario puede realizar fácilmente dos acciones en un archivo en .5 segundos, por lo que lo bajé a 100 y hasta ahora funciona bien. Su C # era un poco fubar (no se convertiría) así que aquí está la versión VB:

Public LastTimeFileWatcherEventRaised As DateTime

If DateTime.Now.Subtract(LastTimeFileWatcherEventRaised).TotalMilliseconds < 100 Then Return LastTimeFileWatcherEventRaised = DateTime.Now .. handle event here