我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用OnChanged事件处理程序来处理事件。我正在使用NotifyFilters。LastWriteTime,但是事件仍然被触发两次。这是代码。

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

在我的情况下,OnChanged被调用两次,当我改变文本文件version.txt并保存它。


当前回答

这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。

首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于2-3个更改文件的通知,而重复时间相对较长(几十秒到几分钟)。

早期建议的最有趣的链接之一是FileSystemWatcher is a Bit Broken。然而,所提出的解决方案只是部分工作,正如同一作者在。net MemoryCache Expiration Demystified的不稳定行为中指出的那样,即使在20秒后也会发出通知。

然后我所做的是基于类似的原则设计一个愚蠢的替代解决方案,没有MemoryCache。

基本上,它创建了一个List<>的项目,其中有一个Key,它是文件的完整路径和一个过期计时器。如果另一个事件再次触发该更改,则在列表中找到该元素,计时器将更新为新的过期时间。 根据经验,过期时间足够长,足以在单个OnStableChange通知中收集多个事件,而不会太长,以至于感觉没有响应。

当你实例化Whatever时,你也将它链接到一个目录和一个非常基本的外部回调。

没有什么是真正优化的,我只是在几行中寻找一个解决方案。

我把它发表在这里

对我来说,这样你就可以在另一个应用程序上验证 某个更聪明、更有经验的人可以改进它,并帮助我了解它的不足之处

    internal class Whatever
    {
        private FileSystemWatcher? watcher = null;

        public delegate void DelegateFileChange(string path);
        public DelegateFileChange? onChange;

        private const int CacheTimeMilliseconds = 200;

        private class ChangeItem
        {
            public delegate void DelegateChangeItem(string key);
            public string Key { get; set; } = "";
            public System.Timers.Timer Expiration = new();
            public DelegateChangeItem? SignalChanged = null;
        }
        private class ChangeCache
        {
            private readonly List<ChangeItem> _changes = new();

            public void Set(string key, int milliSecs, ChangeItem.DelegateChangeItem? signal = null)
            {
                lock (_changes)
                {
                    ChangeItem? existing = _changes.Find(item => item.Key == key);
                    if (existing != null)
                    {
                        existing.Expiration.Interval = milliSecs;
                        existing.SignalChanged = signal;
                    }
                    else
                    {
                        ChangeItem change = new()
                        {
                            Key = key,
                            SignalChanged = signal
                        };
                        change.Expiration.Interval = milliSecs;
                        change.Expiration.AutoReset = false;
                        change.Expiration.Elapsed += delegate { Change_Elapsed(key); };
                        change.Expiration.Enabled = true;
                        _changes.Add(change);
                    }
                }
            }

            private void Change_Elapsed(string key)
            {
                lock (_changes)
                {
                    ChangeItem? existing = _changes.Find(item => item.Key == key);
                    existing?.SignalChanged?.Invoke(key);
                    _changes.RemoveAll(item => item.Key == key);
                }
            }
        }

        private ChangeCache changeCache = new();

        public bool Link(string directory, DelegateFileChange? fileChange = null)
        {
            bool result = false;

            try
            {
                if (Directory.Exists(directory))
                {
                    watcher = new FileSystemWatcher(directory);
                    watcher.NotifyFilter = NotifyFilters.LastWrite;
                    watcher.Changed += Watcher_Changed;

                    onChange = fileChange;

                    watcher.Filter = "*.*";
                    watcher.IncludeSubdirectories = true;
                    watcher.EnableRaisingEvents = true;

                    result = true;
                }
            }
            catch (Exception)
            {
            }

            return result;
        }

        private void OnStableChange(string path)
        {
            if (File.Exists(path))
            {
                onChange?.Invoke(path);
            }
        }

        public void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            changeCache.Set(e.FullPath, CacheTimeMilliseconds, OnStableChange);
        }
    }

其他回答

事件如果没有问,这是一个遗憾,没有现成的解决方案样本f#。 要解决这个问题,这里有我的方法,因为我可以,而且f#是一种很棒的。net语言。

使用FSharp.Control.Reactive包过滤掉重复的事件,它只是响应式扩展的f#包装器。所有这些都可以针对全框架或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

In my case need to get the last line of a text file that is inserted by other application, as soon as insertion is done. Here is my solution. When the first event is raised, i disable the watcher from raising others, then i call the timer TimeElapsedEvent because when my handle function OnChanged is called i need the size of the text file, but the size at that time is not the actual size, it is the size of the file imediatelly before the insertion. So i wait for a while to proceed with the right file size.

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

好吧,这是我如何只引发一个事件的解决方案:

FileSystemWatcheк watcher = new FileSystemWatcher();

//'path' - path to the file that has been modified.
watcher.Changed += (s, e) => FileChanged(path);

这里是FileChanged实现

//count is our counter to triger when we can raise and when not.
private int count = 0;
private void FileChanged(string path)
{
   if (count % 2 == 0)
     {
       //code here
     }

     count ++;
}

您可以尝试打开它进行写入,如果成功,则可以假设其他应用程序已经处理了该文件。

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

仅仅打开它以供写入似乎不会引发已更改的事件。所以应该是安全的。

这里有一个你可以尝试的新解决方案。很适合我。在已更改事件的事件处理程序中,以编程方式从设计器输出中删除处理程序(如果需要的话),然后以编程方式将处理程序添加回来。例子:

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