FileSystemWatcher Changed事件被引发两次

use*_*707 316 c# filesystemwatcher

我有一个应用程序,我正在寻找一个文本文件,如果对文件进行任何更改,我使用OnChangedeventhandler来处理事件.我正在使用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)
{
   .......
}
Run Code Online (Sandbox Code Playgroud)

在我的情况下OnChanged,当我更改文本文件version.txt并保存时,会调用两次.

Jør*_*ode 270

我担心这是该FileSystemWatcher课程中众所周知的错误/特征.这来自该课程的文档:

在某些情况下,您可能会注意到单个创建事件会生成由组件处理的多个Created事件.例如,如果使用FileSystemWatcher组件来监视目录中新文件的创建,然后使用记事本创建文件进行测试,即使只创建了一个文件,也可能会看到生成两个Created事件.这是因为Notepad在写入过程中执行多个文件系统操作.记事本批量写入磁盘,创建文件的内容,然后创建文件属性.其他应用程序可以以相同的方式执行.由于FileSystemWatcher监视操作系统活动,因此将拾取这些应用程序触发的所有事件.

现在这段文本是关于Created事件的,但同样的事情也适用于其他文件事件.在某些应用程序中,您可以通过使用该NotifyFilter属性来解决这个问题,但我的经验表明,有时您还必须进行一些手动重复过滤(hacks).

前一段时间我预订了一个带有一些FileSystemWatcher提示的页面.你可能想看一下.

  • Raymond Chen刚刚在博客中写道:[为什么在记事本中保存文件会触发多个FindFirstChangeNotification事件?](http://blogs.msdn.com/b/oldnewthing/archive/2014/05/07/10523172.aspx) (6认同)
  • 一个不错的解决方案:[FileSystemWatcher 有点损坏](https://failingfast.io/a-robust-solution-for-filesystemwatcher-firing-events-multiple-times/) 和 [FileSystemWatcherMemoryCache 示例](https://github .com/benbhall/FileSystemWatcherMemoryCache)作者:Ben Hall。 (2认同)

Dav*_*ant 145

我在我的委托中使用以下策略"修复"了这个问题:

// 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;
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 我尝试过,如果我一次修改一个文件但是如果我一次修改了两个文件(比如复制1.txt和2.txt到1.txt的副本和2.txt的副本)它就会有效一个事件不是预期的两个. (14认同)
  • 这似乎解决了这个问题,但事实并非如此.如果另一个进程正在进行更改,您可能会丢失它们,它看起来工作的原因是因为另一个进程的IO是异步的,并且在完成处理之前禁用监视,从而与其他可能发生的事件一起创建竞争条件出于兴趣.这就是@ChristopherPainter观察他的问题的原因. (13认同)
  • -1:如果在禁用时发生了您感兴趣的其他更改,该怎么办? (12认同)
  • 这已经过了几个月,但我认为我最终做的是让事件调用一个将业务逻辑放在锁定语句中的方法.这样,如果我得到额外的事件,他们就会排队,直到轮到他们,因为上一次迭代处理了所有事情,所以他们没有任何事情要做. (2认同)
  • @G.Stoynev 你不会明白的。根据您的应用程序需求,这可能是也可能不是问题。就我而言,事实并非如此。 (2认同)
  • @cYounes:除非你异步地做*你的东西*. (2认同)

BaB*_*aBu 103

通过检查相关文件的时间戳,可以检测并丢弃OnChanged来自的任何重复事件.像这样:FileSystemWatcherFile.GetLastWriteTime

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
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案,但是我使用Rx来做"正确"的事情(将"重命名"更改为你感兴趣的事件的名称):`Observable.FromEventPattern <FileSystemEventArgs>(fileSystemWatcher,"Renamed" ").选择(e => e.EventArgs).Distinct(e => e.FullPath).Subscribe(onNext);` (13认同)
  • 因为被触发的事件是分开的,所以不起作用:最后写入时间:636076274162565607最后编写时间:636076274162655722 (10认同)
  • 我错过了什么吗?我不明白这是如何工作的.从我看到的事件同时发生,因此如果他们同时进入上述事件,他们将在lastRead设置之前开始运行. (4认同)
  • 这实际上非常有效.应该是正确的答案. (4认同)
  • 正如阿什解释的那样,不起作用。这将起作用:`if (lastWriteTime.Ticks - lastRead.Ticks &gt; 100000)` (2认同)

Dee*_*hri 21

这是我的解决方案,帮助我阻止事件被提升两次:

watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
Run Code Online (Sandbox Code Playgroud)

这里我设置的NotifyFilter属性只有Filename和size.
watcher是FileSystemWatcher的对象.希望这会有所帮助.

  • 可能会进行真正的更改而不会改变文件的大小,因此这种技术在这种情况下会失败. (28认同)
  • 另外,在记事本中,我创建了一个包含四个字符的文件:abcd.然后我打开了一个新的记事本实例并输入了相同的四个字符.我选择了File | 另存为并选择相同的文件.该文件是相同的,大小和文件名不会更改,因为该文件具有相同的四个字母,所以这不会触发. (8认同)
  • 我猜这是一个相当常见的情况,你知道任何有意义的改变都会修改文件大小(例如,我的情况是附加到日志文件).虽然使用此解决方案的任何人都应该了解(并记录)该假设,但这正是我所需要的. (3认同)
  • @GrandOpener:这并不总是正确的。就我而言,我正在查看其内容仅包含一个字符(0 或 1)的文件。 (2认同)

Men*_*gis 9

我创建了一个带有类的 Git 存储库,该类扩展FileSystemWatcher为仅在复制完成时触发事件。它丢弃除最后一个之外的所有更改事件,并且仅当文件可供读取时才引发它。

下载FileSystemSafeWatcher并将其添加到您的项目中。

然后将其用作正常FileSystemWatcher事件并在事件触发时进行监控。

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Run Code Online (Sandbox Code Playgroud)

  • 谢谢:) 它解决了我的问题.. 希望创建和复制的事件能够与单个观察者一起正常工作以很好地解决这个问题。/sf/ask/3851059271/ (2认同)

Rém*_*ery 8

这是我的方法:

// 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();
}
Run Code Online (Sandbox Code Playgroud)

这是我在一个项目中解决此问题的解决方案,我将该文件作为附件发送到邮件中.即使定时器间隔较小,它也可以轻松避免两次触发事件,但在我的情况下,1000是好的,因为我更幸福的是,错过了一些变化,而不是每秒输入> 1条消息.至少它可以正常工作,以防几个文件在同一时间更改.

我想到的另一个解决方案是用字典映射文件替换列表到它们各自的MD5,因此您不必选择任意间隔,因为您不必删除条目但更新其值,并且如果没有改变,取消你的东西.随着文件被监控并占用越来越多的内存,它有一个字典在内存中增长的缺点,但我已经读过某个地方,监控的文件数量取决于FSW的内部缓冲区,所以可能不那么重要.不知道MD5的计算时间如何影响你的代码的表现,小心=


小智 8

Try with this 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 什么信号量?我在这里只看到一个布尔变量。此外,主要问题尚未解决:FileSystemEventHandler 仍在触发多个事件。这段代码有什么功效呢?`if (let==false) { ... } else { let = false; }` ? 令人难以置信的是,这竟然得到了支持,这肯定只是 StackOverflow 徽章的问题。 (5认同)
  • 这是最好的解决方案,使用信号量而不是计时器。 (2认同)

Iko*_*kon 7

我的方案是我有一台带有Linux服务器的虚拟机.我正在Windows主机上开发文件.当我在主机上的文件夹中更改某些内容时,我希望上传所有更改,并通过Ftp同步到虚拟服务器上.这是我写入文件时删除重复更改事件的方法(标记包含要修改的文件的文件夹):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

主要是我创建一个哈希表来存储文件写入时间信息.然后,如果哈希表具有被修改的文件路径,并且它的时间值与当前通知的文件的更改相同,那么我知道它是事件的副本并忽略它.

  • 不要比较字符串,使用DateTime.Equals() (4认同)

Far*_*ani 5

我知道这是一个老问题,但有同样的问题,上述解决方案都没有真正解决我面临的问题。我创建了一个字典,用 LastWriteTime 映射文件名。因此,如果文件不在字典中,则继续执行该过程,否则请检查上次修改时间是什么时候,如果与字典中的不同,则运行代码。

    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
                }
            }
Run Code Online (Sandbox Code Playgroud)