c# - ejemplo - El evento FileSystemWatcher Changed se levanta dos veces
filesystemwatcher windows service c# (30)
Tengo una aplicación en la que estoy buscando un archivo de texto y, si hay algún cambio realizado en el archivo, estoy usando el OnChanged
eventos OnChanged para manejar el evento. Estoy usando NotifyFilters.LastWriteTime
pero aún así el evento se dispara dos veces. Aquí está el código.
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C://Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
}
En mi caso, se llama a OnChanged
dos veces, cuando cambio el archivo de texto version.txt
y lo version.txt
.
Aquí está mi enfoque:
// Consider having a List<String> named _changedFiles
private void OnChanged(object source, FileSystemEventArgs e)
{
lock (_changedFiles)
{
if (_changedFiles.Contains(e.FullPath))
{
return;
}
_changedFiles.Add(e.FullPath);
}
// do your stuff
System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
{
lock (_changedFiles)
{
_changedFiles.Remove(e.FullPath);
}
};
timer.Start();
}
Esta es la solución que utilicé para resolver este problema en un proyecto donde estaba enviando el archivo como archivo adjunto en un correo. Evitará fácilmente el evento de doble disparo incluso con un intervalo de temporizador más pequeño, pero en mi caso 1000 estuvo bien ya que estaba más feliz con la falta de pocos cambios que con inundar el buzón con> 1 mensaje por segundo. Al menos funciona bien en caso de que se cambien varios archivos al mismo tiempo.
Otra solución en la que pensé sería reemplazar la lista con un diccionario de mapeo a su respectivo MD5, para que no tenga que elegir un intervalo arbitrario ya que no tendría que eliminar la entrada sino actualizar su valor, y cancela tus cosas si no ha cambiado Tiene la desventaja de tener un Diccionario que crece en la memoria a medida que se monitorean los archivos y consume más y más memoria, pero he leído en alguna parte que la cantidad de archivos que se monitorean depende del búfer interno del FSW, así que tal vez no sea tan importante. No sé cómo el tiempo de computación MD5 afectaría el rendimiento de su código, cuidado = /
Aquí está mi solución que me ayudó a detener el evento dos veces:
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
Aquí he establecido la propiedad NotifyFilter
con solo Nombre de archivo y tamaño.
watcher
es mi objeto de FileSystemWatcher. Espero que esto ayude.
Aquí hay una nueva solución que puedes probar. Funciona bien para mi En el controlador de eventos para el evento modificado, elimine programáticamente el controlador del diseñador y envíe un mensaje si así lo desea, luego agregue el controlador de manera programática. ejemplo:
public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
{
fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
MessageBox.Show( "File has been uploaded to destination", "Success!" );
fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
}
Cualquier evento OnChanged
duplicado del FileSystemWatcher
se puede detectar y descartar al verificar la marca de tiempo File.GetLastWriteTime
en el archivo en cuestión. Al igual que:
DateTime lastRead = DateTime.MinValue;
void OnChanged(object source, FileSystemEventArgs a)
{
DateTime lastWriteTime = File.GetLastWriteTime(uri);
if (lastWriteTime != lastRead)
{
doStuff();
lastRead = lastWriteTime;
}
// else discard the (duplicated) OnChanged event
}
Esta solución me funcionó en la aplicación de producción:
Ambiente:
VB.Net Framework 4.5.2
Establecer manualmente las propiedades del objeto: NotifyFilter = Tamaño
Entonces usa este código:
Public Class main
Dim CalledOnce = False
Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
If (CalledOnce = False) Then
CalledOnce = True
If (e.ChangeType = 4) Then
'' Do task...
CalledOnce = False
End If
End Sub
End Sub
Este código funcionó para mí.
private void OnChanged(object source, FileSystemEventArgs e)
{
string fullFilePath = e.FullPath.ToString();
string fullURL = buildTheUrlFromStudyXML(fullFilePath);
System.Diagnostics.Process.Start("iexplore", fullURL);
Timer timer = new Timer();
((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
timer.Start();
}
private void t_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer)sender).Stop();
theWatcher.Changed += new FileSystemEventHandler(OnChanged);
}
He "arreglado" ese problema usando la siguiente estrategia en mi delegado:
// fsw_ is the FileSystemWatcher instance used by my application.
private void OnDirectoryChanged(...)
{
try
{
fsw_.EnableRaisingEvents = false;
/* do my stuff once asynchronously */
}
finally
{
fsw_.EnableRaisingEvents = true;
}
}
He cambiado la forma en que superviso los archivos en los directorios. En lugar de usar el FileSystemWatcher I sondeo las ubicaciones en otro hilo y luego mira el LastWriteTime del archivo.
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
using (var fs = File.OpenWrite(e.FullPath))
{
}
//do your stuff
}
catch (Exception)
{
//no write access, other app not done
}
}
Usando esta información y manteniendo un índice de la ruta de un archivo y su último tiempo de escritura, puedo determinar los archivos que han cambiado o que se han creado en una ubicación en particular. Esto me elimina de las rarezas de FileSystemWatcher. El principal inconveniente es que necesita una estructura de datos para almacenar LastWriteTime y la referencia al archivo, pero es confiable y fácil de implementar.
He creado un repositorio Git con una clase que amplía FileSystemWatcher
para activar los eventos solo cuando se realiza la copia. Descarta todos los eventos modificados, excepto el último, y lo levanta solo cuando el archivo está disponible para su lectura.
Descargue FileSystemSafeWatcher y agréguelo a su proyecto.
Luego, utilícelo como un FileSystemWatcher
normal y supervise cuando se desencadenen los eventos.
DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);
Intenta con este código:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
La razón principal fue que la última vez que accedió al primer evento fue la hora actual (escritura del archivo o hora modificada). entonces el segundo evento fue el último tiempo de acceso original del archivo. Resuelvo bajo código.
var lastRead = DateTime.MinValue;
Watcher = new FileSystemWatcher(...)
{
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
Filter = "*.dll",
IncludeSubdirectories = false,
};
Watcher.Changed += (senderObject, ea) =>
{
var now = DateTime.Now;
var lastWriteTime = File.GetLastWriteTime(ea.FullPath);
if (now == lastWriteTime)
{
return;
}
if (lastWriteTime != lastRead)
{
// do something...
lastRead = lastWriteTime;
}
};
Watcher.EnableRaisingEvents = true;
Me temo que este es un error / característica conocida de la clase FileSystemWatcher
. Esto es de la documentación de la clase:
En ciertas situaciones, puede observar que un solo evento de creación genera múltiples eventos creados que son manejados por su componente. Por ejemplo, si usa un componente FileSystemWatcher para monitorear la creación de nuevos archivos en un directorio y luego lo prueba usando el Bloc de notas para crear un archivo, puede ver dos eventos creados generados aunque solo se haya creado un único 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 realizar de la misma manera. Debido a que FileSystemWatcher supervisa las actividades del sistema operativo, todos los eventos que estas aplicaciones activan serán recogidos.
Ahora, este fragmento de texto es sobre el evento Created
, pero lo mismo se aplica a otros eventos de archivo también. En algunas aplicaciones puede ser capaz de evitar esto mediante el uso de la propiedad NotifyFilter
, pero mi experiencia dice que a veces también tiene que hacer un filtrado manual duplicado (hacks).
Hace un tiempo reservé una página marcada con algunos consejos de FileSystemWatcher . Quizás quieras revisarlo.
Mi escenario es que tengo una máquina virtual con un servidor Linux en ella. Estoy desarrollando archivos en el host de Windows. Cuando cambio algo en una carpeta en el host quiero que todos los cambios se carguen, sincronizados en el servidor virtual a través de FTP. Así es como elimino el evento de cambio duplicado cuando escribo en un archivo (lo que marca la carpeta que contiene el archivo que se va a modificar también):
private Hashtable fileWriteTime = new Hashtable();
private void fsw_sync_Changed(object source, FileSystemEventArgs e)
{
string path = e.FullPath.ToString();
string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString();
// if there is no path info stored yet
// or stored path has different time of write then the one now is inspected
if ( !fileWriteTime.ContainsKey(path) ||
fileWriteTime[path].ToString() != currentLastWriteTime
)
{
//then we do the main thing
log( "A CHANGE has occured with " + path );
//lastly we update the last write time in the hashtable
fileWriteTime[path] = currentLastWriteTime;
}
}
Principalmente creo una tabla hash para almacenar información de tiempo de escritura de archivos. Luego, si la tabla hash tiene la ruta de archivo que se modifica y su valor de tiempo es el mismo que el cambio del archivo notificado actualmente, entonces sé que es el duplicado del evento y lo ignoro.
Pasé una cantidad significativa de tiempo usando FileSystemWatcher, y algunos de los enfoques aquí no funcionarán. Realmente me gustó el enfoque de deshabilitación de eventos, pero desafortunadamente, no funciona si hay más de 1 archivo que se está perdiendo, el segundo archivo se perderá más si no todas las veces. Así que utilizo el siguiente enfoque:
private void EventCallback(object sender, FileSystemEventArgs e)
{
var fileName = e.FullPath;
if (!File.Exists(fileName))
{
// We''ve dealt with the file, this is just supressing further events.
return;
}
// File exists, so move it to a working directory.
File.Move(fileName, [working directory]);
// Kick-off whatever processing is required.
}
Perdón por la excavación de la tumba, pero he estado luchando contra este problema por un tiempo y finalmente se me ocurrió una forma de manejar estos múltiples eventos. Me gustaría dar las gracias a todos en este hilo, ya que lo he usado en muchas referencias al luchar contra este problema.
Aquí está mi código completo. Utiliza un diccionario para rastrear la fecha y hora de la última escritura del archivo. Compara ese valor, y si es el mismo, suprime los eventos. A continuación, establece el valor después de iniciar el nuevo hilo.
var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Podría intentar abrirlo para escribir, y si tiene éxito, podría asumir que la otra aplicación ha terminado con el archivo.
using System.Threading; // used for backgroundworker
using System.Diagnostics; // used for file information
private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events
private void fswFileWatch_Changed( object sender, FileSystemEventArgs e )
{
try
{
//check if we already have this value in our dictionary.
if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) )
{
//compare timestamps
if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() )
{
//lock the table
lock ( fileModifiedTable )
{
//make sure our file is still valid
if ( File.Exists( e.FullPath ) )
{
// create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers
BackgroundWorker newThreadWork = new BackgroundWorker();
newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork );
newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted );
// capture the path
string eventFilePath = e.FullPath;
List<object> arguments = new List<object>();
// add arguments to pass to the background worker
arguments.Add( eventFilePath );
arguments.Add( newEvent.File_Modified );
// start the new thread with the arguments
newThreadWork.RunWorkerAsync( arguments );
fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file.
FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on.
}
}
}
}
}
catch ( IOException IOExcept )
{
//catch any errors
postError( IOExcept, "fswFileWatch_Changed" );
}
}
Apenas abriéndolo para escribir parece que no se levanta el evento cambiado. Entonces debería ser seguro.
Pude hacer esto agregando una función que verifica duplicados en una matriz de búfer.
Luego realice la acción después de que la matriz no haya sido modificada durante X tiempo usando un temporizador: - Reinicie el temporizador cada vez que se escriba algo en el búfer - Realice la acción en el tick
Esto también atrapa otro tipo de duplicación. Si modifica un archivo dentro de una carpeta, la carpeta también lanza un evento de cambio.
Function is_duplicate(str1 As String) As Boolean
If lb_actions_list.Items.Count = 0 Then
Return False
Else
Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim
If compStr <> str1 AndAlso compStr.parentDir <> str1 & "/" Then
Return False
Else
Return True
End If
End If
End Function
Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
Return aString.Substring(0, CInt(InStrRev(aString, "/", aString.Length - 1)))
End Function
End Module
Sé que este es un problema antiguo, pero tenía el mismo problema y ninguna de las soluciones anteriores realmente resolvió el problema al que me enfrentaba. He creado un diccionario que asigna el nombre del archivo con LastWriteTime. Por lo tanto, si el archivo no está en el diccionario, continuará con el proceso. De otra manera, verifique cuándo fue la última vez que se modificó y si es diferente de lo que contiene el diccionario.
Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>();
private void OnChanged(object source, FileSystemEventArgs e)
{
if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
{
dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
//your code here
}
}
Si no se solicita el evento, es una pena que no haya muestras de soluciones listas para F #. Para solucionar esto, aquí está mi receta, solo porque puedo y F # es un maravilloso lenguaje .NET.
Los eventos duplicados se filtran utilizando el paquete FSharp.Control.Reactive
, que es solo un contenedor F # para extensiones reactivas. Todo lo que se puede orientar a marco completo o netstandard2.0
:
let createWatcher path filter () =
new FileSystemWatcher(
Path = path,
Filter = filter,
EnableRaisingEvents = true,
SynchronizingObject = null // not needed for console applications
)
let createSources (fsWatcher: FileSystemWatcher) =
// use here needed events only.
// convert `Error` and `Renamed` events to be merded
[| fsWatcher.Changed :> IObservable<_>
fsWatcher.Deleted :> IObservable<_>
fsWatcher.Created :> IObservable<_>
//fsWatcher.Renamed |> Observable.map renamedToNeeded
//fsWatcher.Error |> Observable.map errorToNeeded
|] |> Observable.mergeArray
let handle (e: FileSystemEventArgs) =
printfn "handle %A event ''%s'' ''%s'' " e.ChangeType e.Name e.FullPath
let watch path filter throttleTime =
// disposes watcher if observer subscription is disposed
Observable.using (createWatcher path filter) createSources
// filter out multiple equal events
|> Observable.distinctUntilChanged
// filter out multiple Changed
|> Observable.throttle throttleTime
|> Observable.subscribe handle
[<EntryPoint>]
let main _args =
let path = @"C:/Temp/WatchDir"
let filter = "*.zip"
let throttleTime = TimeSpan.FromSeconds 10.
use _subscription = watch path filter throttleTime
System.Console.ReadKey() |> ignore
0 // return an integer exit code
Sobre todo para mi futuro :)
Escribí una envoltura usando Rx:
public class WatcherWrapper : IDisposable
{
private readonly FileSystemWatcher _fileWatcher;
private readonly Subject<FileSystemEventArgs> _infoSubject;
private Subject<FileSystemEventArgs> _eventSubject;
public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
{
_fileWatcher = new FileSystemWatcher(path, nameFilter);
if (notifyFilters != null)
{
_fileWatcher.NotifyFilter = notifyFilters.Value;
}
_infoSubject = new Subject<FileSystemEventArgs>();
_eventSubject = new Subject<FileSystemEventArgs>();
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
// this takes care of double events and still works with changing the name of the same file after a while
_infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
.Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
infos =>
{
if (infos != null)
foreach (var info in infos)
{
{
_eventSubject.OnNext(info);
}
}
});
_fileWatcher.EnableRaisingEvents = true;
}
public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;
public void Dispose()
{
_fileWatcher?.Dispose();
_eventSubject.Dispose();
_infoSubject.Dispose();
}
}
Uso:
var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)
Tengo una solución muy rápida y simple aquí, a mí me funciona, y sin importar que el evento se dispare una o dos veces o más de vez en cuando, revísalo:
private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
fireCount++;
if (fireCount == 1)
{
MessageBox.Show("Fired only once!!");
dowork();
}
else
{
fireCount = 0;
}
}
}
Un posible ''hack'' sería acelerar los eventos usando las extensiones reactivas, por ejemplo:
var watcher = new FileSystemWatcher("./");
Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
.Throttle(new TimeSpan(500000))
.Subscribe(HandleChangeEvent);
watcher.EnableRaisingEvents = true;
En este caso estoy acelerando a 50 ms, en mi sistema era suficiente, pero los valores más altos deberían ser más seguros. (Y como he dicho, sigue siendo un ''hack'').
¡Prueba esto!
string temp="";
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C://Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
if(temp=="")
{
//do thing you want.
temp = e.name //name of text file.
}else if(temp !="" && temp != e.name)
{
//do thing you want.
temp = e.name //name of text file.
}else
{
//second fire ignored.
}
}
Me acerqué al problema de creación doble como este, que ignora el primer evento:
Private WithEvents fsw As New System.IO.FileSystemWatcher
Private complete As New List(Of String)
Private Sub fsw_Created(ByVal sender As Object, _
ByVal e As System.IO.FileSystemEventArgs) Handles fsw.Created
If Not complete.Contains(e.FullPath) Then
complete.Add(e.FullPath)
Else
complete.Remove(e.FullPath)
Dim th As New Threading.Thread(AddressOf hprocess)
th.Start(e)
End If
End Sub
Si se registra en el evento OnChanged, al eliminar el archivo monitoreado antes de cambiarlo podría funcionar, siempre que solo necesite monitorear el evento OnChange.
Simplemente agrego un dupe check de la siguiente manera:
private void OnChanged(object source, FileSystemEventArgs e)
{
string sTabName = Path.GetFileNameWithoutExtension(e.Name);
string sLastLine = ReadLastLine(e.FullPath);
if(sLastLine != _dupeCheck)
{
TabPage tp = tcLogs.TabPages[sTabName];
TextBox tbLog = (TextBox)tp.Controls[0] as TextBox;
tbLog.Invoke(new Action(() => tbLog.AppendText(sLastLine + Environment.NewLine)));
tbLog.Invoke(new Action(() => tbLog.SelectionStart = tbLog.Text.Length));
tbLog.Invoke(new Action(() => tbLog.ScrollToCaret()));
_dupeCheck = sLastLine;
}
}
public static String ReadLastLine(string path)
{
return ReadLastLine(path, Encoding.Default, "/n");
}
public static String ReadLastLine(string path, Encoding encoding, string newline)
{
int charsize = encoding.GetByteCount("/n");
byte[] buffer = encoding.GetBytes(newline);
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
long endpos = stream.Length / charsize;
for (long pos = charsize; pos < endpos; pos += charsize)
{
stream.Seek(-pos, SeekOrigin.End);
stream.Read(buffer, 0, buffer.Length);
if (encoding.GetString(buffer) == newline)
{
buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return encoding.GetString(buffer);
}
}
}
return null;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private const int WM_VSCROLL = 0x115;
private const int SB_BOTTOM = 7;
/// <summary>
/// Scrolls the vertical scroll bar of a multi-line text box to the bottom.
/// </summary>
/// <param name="tb">The text box to scroll</param>
public static void ScrollToBottom(TextBox tb)
{
SendMessage(tb.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero);
}
Tuve que combinar varias ideas de las publicaciones anteriores y agregar el control de bloqueo de archivos para que funcione:
FileSystemWatcher fileSystemWatcher;
private void DirectoryWatcher_Start()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
{
Path = @"c:/mypath",
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.*",
EnableRaisingEvents = true
};
fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}
private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
Int32 waitMS = 250;
Int32 currentMS = 0;
FileInfo file = new FileInfo(fullPath);
FileStream stream = null;
do
{
try
{
stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
stream.Close();
callback(fullPath);
return;
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Dispose();
}
Thread.Sleep(waitMS);
currentMS += waitMS;
} while (currentMS < timeoutMS);
}
private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();
private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
try
{
lock (DirectoryWatcher_fileLastWriteTimeCache)
{
DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
{
if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
return; // file was already handled
}
DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
}
Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
{
// do the job with fullPath...
}));
}
catch (Exception e)
{
// handle exception
}
}
Muchas de estas respuestas son impactantes, de verdad. Aquí hay algo de código de mi biblioteca de control de XanderUI que corrige esto.
private void OnChanged(object sender, FilesystemEventArgs e)
{
if (FSWatcher.IncludeSubdirectories == true)
{
if (File.Exists(e.FullPath)) { DO YOUR FILE CHANGE STUFF HERE... }
}
else DO YOUR DIRECTORY CHANGE STUFF HERE...
}
Que sea simple definir una variable global var1 = true
.
Private Sub FileWatchman_Changed(ByVal sender As System.Object, ByVal e As System.IO.FileSystemEventArgs) Handles FileWatchman.Changed
If var1 = true
your logic goes here
var1 = false
Else
var1 = true
End If
End Sub
FileReadTime = DateTime.Now;
private void File_Changed(object sender, FileSystemEventArgs e)
{
var lastWriteTime = File.GetLastWriteTime(e.FullPath);
if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
{
// code
FileReadTime = DateTime.Now;
}
}