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. (?)
Escribí un código que resuelve este problema y otras características interesantes de FileSystemWatcher. Está publicado en mi blog en: http://precisionsoftware.blogspot.com/2009/05/filesystemwatcher-done-right.html
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