web-dev-qa-db-fra.com

L'événement FileSystemWatcher Changed est déclenché deux fois

J'ai une application dans laquelle je cherche un fichier texte et si des modifications sont apportées au fichier, j'utilise le gestionnaire d'événements OnChanged pour gérer l'événement. J'utilise le NotifyFilters.LastWriteTime mais l'événement est toujours déclenché deux fois. Voici le code.

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)
{
   .......
}

Dans mon cas, la OnChanged est appelée deux fois, lorsque je modifie le fichier texte version.txt et le sauvegarde.

300
user214707

J'ai bien peur qu'il s'agisse d'un bug/d'une fonctionnalité bien connu de la classe FileSystemWatcher. Ceci est tiré de la documentation de la classe:

Vous remarquerez peut-être que dans certaines situations, un seul événement de création génère plusieurs événements créés gérés par votre composant. Par exemple, si vous utilisez un composant FileSystemWatcher pour surveiller la création de nouveaux fichiers dans un répertoire, puis que vous le testez à l'aide du Bloc-notes pour créer un fichier, deux événements créés peuvent être générés alors qu'un seul fichier a été créé. En effet, le Bloc-notes effectue plusieurs actions sur le système de fichiers pendant le processus d’écriture. Le Bloc-notes écrit sur le disque par lots qui créent le contenu du fichier, puis les attributs de fichier. D'autres applications peuvent fonctionner de la même manière. Étant donné que FileSystemWatcher surveille les activités du système d'exploitation, tous les événements déclenchés par ces applications seront récupérés.

Maintenant, ce texte concerne l’événement Created, mais la même chose s’applique également aux autres événements de fichier. Dans certaines applications, vous pourrez peut-être contourner ce problème en utilisant la propriété NotifyFilter, mais mon expérience montre que parfois, vous devez également effectuer un filtrage manuel des doublons (hacks).

Il y a quelque temps, j'ai réservé une page marquée avec quelques astuces FileSystemWatcher . Vous voudrez peut-être y jeter un coup d'œil.

263
Jørn Schou-Rode

J'ai "résolu" ce problème en utilisant la stratégie suivante dans mon délégué:

// 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;
   }
}
142
David Brabant

Tous les événements OnChanged dupliqués de la FileSystemWatcher peuvent être détectés et ignorés en vérifiant l'horodatage File.GetLastWriteTime du fichier en question. Ainsi: 

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
}
102
BaBu

Voici ma solution qui m'a aidé à empêcher que l'événement ne soit soulevé deux fois:

watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;

Ici, j'ai défini la propriété NotifyFilter avec uniquement le nom de fichier et la taille.
watcher est mon objet de FileSystemWatcher. J'espère que cela aidera.

21
Deepashri

Mon scénario est que j'ai une machine virtuelle avec un serveur Linux. Je développe des fichiers sur l'hôte Windows. Lorsque je modifie quelque chose dans un dossier de l'hôte, je souhaite que toutes les modifications soient téléchargées et synchronisées sur le serveur virtuel via FTP. C’est ainsi que j’élimine l’événement de modification des doublons lorsque j’écris dans un fichier (qui marque également le dossier contenant le fichier à modifier):

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;
    }
}

Je crée principalement une table de hachage pour stocker les informations de temps d'écriture de fichier. Ensuite, si la table de hachage a le chemin d'accès au fichier modifié et que sa valeur temporelle est identique à celle du fichier actuellement notifié, je sais qu'il s'agit du doublon de l'événement et l'ignore.

8
Ikon

Voici mon approche:

// 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();
}

C’est la solution que j’avais utilisée pour résoudre ce problème sur un projet dans lequel j’envoyais le fichier en pièce jointe dans un courrier . Cela évite facilement l’événement à deux déclenchements même avec un intervalle de temps plus petit, mais dans mon cas 1000 était correct depuis. J'étais plus heureux de ne manquer que de quelques modifications plutôt que d'inonder la boîte aux lettres de> 1 message par seconde . Au moins, cela fonctionne parfaitement si plusieurs fichiers sont modifiés exactement au même moment.

Une autre solution à laquelle je pensais serait de remplacer la liste par un fichier de mappage de dictionnaire dans leur MD5 respectif, de sorte que vous n’ayez pas à choisir un intervalle arbitraire car vous n’auriez pas à supprimer l’entrée mais à en mettre à jour la valeur, et annulez vos documents s’ils n’ont pas changé . L’inconvénient de la croissance du dictionnaire est que les fichiers sont surveillés et qu’ils consomment de plus en plus de mémoire, mais j’ai lu quelque part que la quantité de fichiers surveillés dépend de la La mémoire tampon interne de FSW, donc peut-être pas si critique… .. Ne savez pas comment le temps de calcul de MD5 affecterait les performances de votre code non plus, attention = \

6
Rémy Esmery

Essayez avec ce code:

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;
    }
}
4
Jamie Krcmar

Je sais que c’est un vieux problème, mais le problème est le même et aucune des solutions ci-dessus n’a vraiment fait l’effet du problème auquel je faisais face. J'ai créé un dictionnaire qui mappe le nom du fichier avec LastWriteTime. Ainsi, si le fichier ne figure pas dans le dictionnaire, le processus est exécuté d'une autre manière. Vérifiez si l'heure de la dernière modification a été modifiée et si est différent de ce qu'il est dans le dictionnaire, exécutez le code.

    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
                }
            }
4
Farzan Majdani

Voici une nouvelle solution que vous pouvez essayer. Fonctionne bien pour moi. Dans le gestionnaire d'événements de l'événement modifié, supprimez par programme le gestionnaire du concepteur et émettez un message si vous le souhaitez, puis ajoutez le gestionnaire par programme. Exemple: 

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 );
    }
3
Fancy_Mammoth

J'ai une solution de contournement très simple et rapide ici, cela fonctionne pour moi, et peu importe que l'événement soit déclenché une ou deux fois ou plus à l'occasion, consultez-le:

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;
        }
    }
}
3
Xiaoyuvax

surtout pour l'avenir moi :)

J'ai écrit un wrapper utilisant 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();
    }
}

Usage:

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)
2

La raison principale était que L'heure du dernier accès au premier événement était l'heure actuelle (écriture du fichier ou heure modifiée) .

        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;
2
Kim Ki Won

Ce code a fonctionné pour moi.

        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);
    }
2
Mister Positive

J'ai créé un référentiel Git avec une classe qui étend FileSystemWatcher pour déclencher les événements uniquement lorsque la copie est terminée. Il supprime tous les événements modifiés sauf le dernier et ne le déclenche que lorsque le fichier est disponible pour la lecture.

Téléchargez FileSystemSafeWatcher et ajoutez-le à votre projet.

Ensuite, utilisez-le comme une variable normale FileSystemWatcher et surveillez le moment où les événements sont déclenchés.

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
2
Menelaos Vergis

Un «hack» possible serait de limiter les événements en utilisant Reactive Extensions, par exemple:

var watcher = new FileSystemWatcher("./");

Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
            .Throttle(new TimeSpan(500000))
            .Subscribe(HandleChangeEvent);

watcher.EnableRaisingEvents = true;

Dans ce cas, je suis limité à 50 ms, ce qui était suffisant sur mon système, mais des valeurs plus élevées devraient être plus sûres. (Et comme je l'ai dit, c'est toujours un "hack").

2
TimothyP

J'ai passé beaucoup de temps à utiliser FileSystemWatcher et certaines des approches décrites ici ne fonctionneront pas. J'ai bien aimé l'approche par les événements désactivants, mais malheureusement, cela ne fonctionne pas s'il y a plus d'un fichier en train d'être supprimé, le deuxième fichier manquera presque toujours. J'utilise donc l'approche suivante:

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.
}
2
Gino

Essayez ceci, ça fonctionne bien

  private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
    static void Main(string[] args)
    {
        Console.WriteLine("Watching....");

        Watcher.Path = @"D:\Temp\Watcher";
        Watcher.Changed += OnChanged;
        Watcher.EnableRaisingEvents = true;
        Console.ReadKey();
    }

    static void OnChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            Watcher.Changed -= OnChanged;
            Watcher.EnableRaisingEvents = false;
            Console.WriteLine($"File Changed. Name: {e.Name}");
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
        finally
        {
            Watcher.Changed += OnChanged;
            Watcher.EnableRaisingEvents = true;
        }
    }
1
Ken

J'ai changé la façon dont je surveille les fichiers dans les répertoires. Au lieu d'utiliser les emplacements d'interrogation FileSystemWatcher I sur un autre thread, puis examinez le LastWriteTime du fichier.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

En utilisant ces informations et en conservant l'index d'un chemin de fichier et sa dernière heure d'écriture, je peux déterminer les fichiers modifiés ou créés à un emplacement particulier. Cela me supprime des bizarreries de FileSystemWatcher. Le principal inconvénient est que vous avez besoin d'une structure de données pour stocker LastWriteTime et la référence au fichier, mais elle est fiable et facile à mettre en œuvre.

1
Wil P

Je voulais réagir uniquement sur le dernier événement, juste au cas où, également sur un changement de fichier Linux, il semblait que le fichier était vide lors du premier appel, puis rempli à nouveau lors du prochain appel et ne me dérangeait pas de perdre un peu de temps au cas où le système d'exploitation décidé de faire un changement de fichier/attribut.

J'utilise async .NET ici pour m'aider à faire le threading.

    private static int _fileSystemWatcherCounts;
    private async void OnChanged(object sender, FileSystemEventArgs e)
    {
        // Filter several calls in short period of time
        Interlocked.Increment(ref _fileSystemWatcherCounts);
        await Task.Delay(100);
        if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
            DoYourWork();
    }
1
Guillermo Ruffino

Désolé pour la tombe, mais je lutte contre ce problème depuis un moment maintenant et j'ai finalement trouvé un moyen de gérer ces événements multiples. Je voudrais remercier tout le monde dans ce fil de discussion car je l'ai utilisé dans de nombreuses références pour lutter contre ce problème.

Voici mon code complet. Il utilise un dictionnaire pour suivre la date et l'heure de la dernière écriture du fichier. Il compare cette valeur et, si elle est identique, supprime les événements. Il définit ensuite la valeur après le démarrage du nouveau thread.

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" );
        }
    }
1
BinaryAssault

Vous pouvez essayer de l'ouvrir en écriture et, en cas de succès, vous pouvez supposer que l'autre application est terminée avec le fichier.

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
    }
}

Le simple fait de l’ouvrir en écriture ne semble pas déclencher l’événement modifié. Donc, cela devrait être sûr.

1
MatteKarla

Événement si non demandé, il est dommage qu'il n'y ait pas d'échantillons de solution prête pour F #. Pour résoudre ce problème, voici ma recette, simplement parce que je peux et que F # est un merveilleux langage .NET.

Les événements en double sont filtrés à l'aide du package FSharp.Control.Reactive, qui n'est qu'un encapsuleur F # pour les extensions réactives. Tout ce qui peut être ciblé sur la structure complète ou 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
1
python_kaa

Beaucoup de ces réponses sont vraiment choquantes… .. Heres du code de ma bibliothèque de contrôles XanderUI qui résout ce problème.

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...
}
0
Ricky Divjakovski

Je devais combiner plusieurs idées des articles ci-dessus et ajouter une vérification de verrouillage de fichier pour que cela fonctionne pour moi:

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
    }
}
0
HarryP

J'ajoute simplement un test de dupe comme suit:

 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);
    }
0
Adamus Turner

Essaye ça!

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.
}

}
0
L T

Code avec désactivation personnalisable de l'intervalle de temps de blocage du deuxième observateur en hausse et sans blocage des observateurs s'ils existent:

    namespace Watcher
    {
        class Static
        {
            public static DateTime lastDomain { get; set; }
            public static string lastDomainStr { get; set; }
        }
        public partial class Form1 : Form
       {
            int minMs = 20;//time for blocking in ms
            public Form1()
            {
                InitializeComponent();
                Static.lastDomain = new DateTime(1970, 1, 1, 0, 0, 0);
                Static.lastDomainStr = "";  
                Start();
            }
             private void Start()//Start watcher
             {
                //...
                domain.Changed += new FileSystemEventHandler(Domain);
                domain.EnableRaisingEvents = true;
                //...you second unblocked watchers
                second.Changed += new FileSystemEventHandler(Second);
                second.EnableRaisingEvents = true;
             }
             private void Domain(object source, FileSystemEventArgs e)
             {
                if (now.Subtract(Static.lastDomain).TotalMilliseconds < minMs && Static.lastDomainStr == e.FullPath)return;
                 //...you code here
                 /* if you need form access
                 this.Invoke(new MethodInvoker(() =>{ textBox1.Text = "...";}));
                 */
                 Static.lastDomain = DateTime.Now;
                 Static.lastDomainStr = e.FullPath;
             }
             private void Second(object source, FileSystemEventArgs e)
             {
                  //...Second rised
             }
       }
    }
0
Garric

Dans mon cas, vous devez obtenir la dernière ligne d'un fichier texte inséré par une autre application, dès que l'insertion est terminée. Voici ma solution. Lorsque le premier événement est déclenché, je désactive l’observateur d’élever les autres, puis j’appelle le temporisateur TimeElapsedEvent car, lorsque ma fonction de traitement est appelée OnChanged, j’ai besoin de la taille du fichier texte, mais la taille à ce moment n’est pas la taille réelle, c'est la taille du fichier immédiatement avant l'insertion. J'attends donc un moment avant de procéder avec la bonne taille de fichier. 

private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;

...

private void OnChanged(object source, FileSystemEventArgs e)
   {
    System.Timers.Timer t = new System.Timers.Timer();
    try
    {
        watcher.Changed -= new FileSystemEventHandler(OnChanged);
        watcher.EnableRaisingEvents = false;

        t.Interval = 500;
        t.Elapsed += (sender, args) => t_Elapsed(sender, e);
        t.Start();
    }
    catch(Exception ex) {
        ;
    }
}

private void t_Elapsed(object sender, FileSystemEventArgs e) 
   {
    ((System.Timers.Timer)sender).Stop();
       //.. Do you stuff HERE ..
     watcher.Changed += new FileSystemEventHandler(OnChanged);
     watcher.EnableRaisingEvents = true;
}
0
André Washington
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;
    }
}
0
prasad

J'ai abordé le problème de la double création comme ceci, qui ignore le premier événement:

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
0
Simon Barnett

Pour ce faire, j'ai ajouté une fonction qui vérifie les doublons dans un tableau de mémoire tampon. 

Exécutez ensuite l’action après que le tableau n’a pas été modifié pendant X heure à l’aide d’une minuterie: - Réinitialiser le chronomètre chaque fois que quelque chose est écrit dans le tampon - Effectuer une action sur tick

Cela intercepte également un autre type de duplication. Si vous modifiez un fichier dans un dossier, ce dernier génère également un événement Change.

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
0
blindguy

Cette solution a fonctionné pour moi sur l'application de production:

Environnement:

VB.Net Framework 4.5.2

Définissez manuellement les propriétés de l'objet: NotifyFilter = Size

Alors utilisez ce code:

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
0
wpcoder