FileSystemWatcher Changed 事件引发两次

Posted

技术标签:

【中文标题】FileSystemWatcher Changed 事件引发两次【英文标题】:FileSystemWatcher Changed event is raised twice 【发布时间】:2010-12-18 09:35:34 【问题描述】:

我有一个应用程序,我在其中查找文本文件,如果对文件进行了任何更改,我将使用 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 并保存它时。

【问题讨论】:

这是一种变通方法,但应该以变通方法的质量来判断。跟踪更改非常有效,而且很简单。 OP 正在寻求一种抑制重复事件的方法,这就是下面给出的响应。 msdn.microsoft.com/en-us/library/… 解释说多个事件可能是由防病毒或其他“复杂的文件系统东西”引起的(这听起来像是一个借口)。 我最近打开了这个问题github.com/Microsoft/dotnet/issues/347 我创建了一个类,可以帮助您只获得一个事件。您可以从github.com/melenaos/FileSystemSafeWatcher获取代码 【参考方案1】:

恐怕这是FileSystemWatcher 类的一个众所周知的错误/功能。这是来自类的文档:

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

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

不久前,我用几个FileSystemWatcher tips 标记了一个页面。您可能想检查一下。

【讨论】:

Raymond Chen 刚刚写了一篇博客:Why does saving a file in Notepad fire multiple FindFirstChangeNotification events? 一个不错的解决方案:FileSystemWatcher is a Bit Broken 和 FileSystemWatcherMemoryCache examples by Ben Hall。【参考方案2】:

我已在我的委托中使用以下策略“修复”了该问题:

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

【讨论】:

我试过了,如果我一次修改一个文件,但如果我一次修改两个文件(比如将 1.txt 和 2.txt 复制到 1.txt 的副本和2.txt ) 它只会引发一个事件而不是预期的两个事件。 已经几个月了,但我认为我最终做的是让事件调用一个将业务逻辑放入锁定语句的方法。这样,如果我有额外的事件,他们就会排队等待轮到他们,而且他们无事可做,因为上一次迭代已经处理了所有事情。 这似乎解决了这个问题,但它没有。如果另一个进程正在进行更改,您可能会丢失它们,它似乎工作的原因是因为另一个进程的 IO 是异步的,并且您在完成处理之前禁用监控,从而创建与其他可能发生的事件的竞争条件出于兴趣。这就是@ChristopherPainter 观察他的问题的原因。 -1:如果禁用时发生您感兴趣的另一项更改怎么办? @cYounes : 除非你你的东西是异步的。【参考方案3】:

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

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

【讨论】:

我喜欢那个解决方案,但我使用 Rx 做了“正确”的事情(将 "Rename" 更改为您感兴趣的事件的名称):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext); 我错过了什么吗?我不明白这将如何工作。从我看到的事件同时触发,所以如果它们同时进入上述事件,它们都将在设置 lastRead 之前开始运行。 由于DateTime 的分辨率只有毫秒,因此即使您将File.GetLastWriteTime 替换为DateTime.Now,此方法也有效。根据您的情况,您还可以在全局变量中使用a.FullName 来检测重复事件。 不起作用,因为触发的事件相隔一段时间:上次写入时间:636076274162565607 上次写入时间:636076274162655722 不像 Asheh 解释的那样工作。这将起作用:if (lastWriteTime.Ticks - lastRead.Ticks > 100000)【参考方案4】:

这是我的解决方案,它帮助我阻止了两次引发事件:

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

这里我设置了NotifyFilter 属性,只有文件名和大小。watcher 是我的 FileSystemWatcher 对象。希望这会有所帮助。

【讨论】:

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

我的场景是我有一个带有 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;
    

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

【讨论】:

我假设您定期清空哈希表。 这将精确到秒,但如果两次更改之间的时间长到足以通过一秒,它将失败。此外,如果您想要更高的准确性,您可以使用ToString("o"),但要为更多的失败做好准备。 不要比较字符串,使用 DateTime.Equals() 不,不要。他们不平等。就我当前的项目而言,它们相隔大约一毫秒。我使用 (newtime-oldtime).TotalMilliseconds 【参考方案6】:

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

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

然后将其用作普通的FileSystemWatcher 并监控事件何时触发。

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

【讨论】:

在目录上引发事件时,这似乎失败了。我通过在打开文件之前包装目录检查来让它工作 尽管示例中有错字,但这对我来说似乎是一个可行的解决方案。但是,就我而言,在一秒钟内可能会有十几个更新,所以我不得不大幅降低 _consolidationInterval 以免错过任何更改。虽然 10 毫秒似乎很好,但如果我将 _consolidationInterval 设置为 50 毫秒,我仍然会丢失大约 50% 的更新。我仍然需要进行一些测试才能找到最适合的值。 _consolidationInterval 似乎对我有用。我希望有人分叉这个并使其成为 NuGet 包。 谢谢 :) 它解决了我的问题。希望创建和复制的事件能够与单个观察者一起正常工作,从而很好地解决这个问题。 ***.com/questions/55015132/… 为我的应用工作。非常感谢【参考方案7】:

这是我的方法:

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

这是我在一个项目中用来解决这个问题的解决方案,我在邮件中将文件作为附件发送。 即使使用较小的计时器间隔,它也可以轻松避免两次触发的事件,但在我的情况下,1000 还可以,因为与每秒超过 1 条消息的邮箱泛滥相比,我更高兴错过了一些更改。 至少在同时更改多个文件的情况下它工作得很好。

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

【讨论】:

您的解决方案非常适合我。只是,您忘记将文件添加到 _changedFiles 列表中。代码的第一部分应如下所示:lock (_changedFiles) if (_changedFiles.Contains(e.FullPath)) return; _changedFiles.Add(e.FullPath); // add this! // do your stuff 您的解决方案不是线程安全的。 _changedFiles 是从多个线程访问的。解决它的一种方法是使用ConcurrentDictionary 而不是List。另一种方法是将当前的Form 分配给Timer.SynchronizingObject 属性以及FileSystemWatcher.SynchronizingObject 属性。 @TheodorZoulias 你是否让它与 ConcurrentDicitonary 一起工作?因为对我来说,List 可以正常工作,但ConcurrentDicitonary 会出现问题。 @PriyankPanchal 使用 ConcurrentDicitonary 有点复杂,因为它需要使用此类的专用并发 API。从上面的评论中使用 davidthegrey 的建议可能更容易,只需在访问 List&lt;string&gt; 之前添加 lock (_changedFiles)。否则,如果您只是依靠避免non-thread-safe List&lt;T&gt; 类的并发突变的好运,我也向您致以最良好的祝愿,祝您永远好运。 ??【参考方案8】:

试试这个代码:

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;
    

【讨论】:

什么信号量?我在这里只看到一个布尔变量。此外,主要问题没有解决: FileSystemEventHandler 仍在触发多个事件。这段代码有什么作用? if (let==false) ... else let = false; ?难以置信这是如何获得支持的,这一定只是 *** 徽章的问题。【参考方案9】:

我知道这是一个老问题,但遇到了同样的问题,上述解决方案都没有真正解决我所面临的问题。我创建了一个字典,它将文件名与 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
                
            

【讨论】:

这是一个可靠的解决方案,但它缺少一行代码。在your code here 部分,您应该添加或更新 dateTimeDictionary。 dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath); 对我不起作用。我的更改处理程序被调用了两次,并且该文件第二次具有不同的时间戳。可能是因为它是一个大文件,并且第一次写入正在进行中。我发现折叠重复事件的计时器效果更好。【参考方案10】:

一种可能的“破解”方法是使用响应式扩展来限制事件,例如:

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

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

watcher.EnableRaisingEvents = true;

在这种情况下,我将限制为 50 毫秒,在我的系统上这已经足够了,但更高的值应该更安全。 (就像我说的,它仍然是一个“黑客”)。

【讨论】:

我用过.Distinct(e =&gt; e.FullPath),我觉得处理起来更直观。您已经恢复了 API 预期的行为。【参考方案11】:

我在使用 FileSystemWatcher 上花费了大量时间,但这里的一些方法不起作用。我真的很喜欢禁用事件的方法,但不幸的是,如果有 >1 个文件被删除,它就不起作用,如果不是所有时候,第二个文件将会丢失。 所以我使用以下方法:

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.

【讨论】:

谢谢,这个话题已经讨论过很多次了,也提出了很多变通方法。我认为你的方法是正确而简洁的。谢谢,安东尼·佩里斯【参考方案12】:

这是您可以尝试的新解决方案。对我来说效果很好。在更改事件的事件处理程序中,如果需要,以编程方式从设计器输出消息中删除处理程序,然后以编程方式将处理程序添加回来。示例:

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

【讨论】:

您不需要调用委托类型的构造函数。 this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed; 应该做正确的事。 @bartonjs 谢谢。我不确定为什么要调用整个构造函数。老实说,这很可能是新手的错误。无论如何,我的修复方法似乎效果很好。【参考方案13】:

主要原因是 第一个事件的最后访问时间是当前时间(文件写入或更改时间)。 然后第二个事件是文件的原始上次访问时间。 我在代码下解决。

        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;

【讨论】:

同this answer【参考方案14】:

我在这里有一个非常快速和简单的解决方法,它确实对我有用,并且无论事件是否会偶尔触发一次或两次或更多次,请检查一下:

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;
        
    

【讨论】:

起初我以为这对我有用,但事实并非如此。我有一种情况,文件内容有时只是被覆盖,有时文件被删除并重新创建。虽然您的解决方案似乎在文件被覆盖的情况下有效,但在重新创建文件的情况下并不总是有效。在后一种情况下,事件有时会丢失。 尝试将不同类型的事件分门别类分别处理,我只是提供一个可能的解决方法。祝你好运。 虽然没有测试,但我不太确定这不适用于创建和删除。它在理论上也应该适用。因为 fireCount++ 和 if() 语句都是原子的,不会等待。即使两个触发事件相互竞争。我想一定有别的东西给你带来了麻烦。 (迷路了?什么意思?)【参考方案15】:

这段代码对我有用。

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

【讨论】:

【参考方案16】:

主要是为了未来的我:)

我使用 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();
    

用法:

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)

【讨论】:

【参考方案17】:

试试这个,效果很好

  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;
        
    

【讨论】:

【参考方案18】:

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

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
    

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

【讨论】:

【参考方案19】:
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;
    

【讨论】:

虽然这可能是对所提问题的最佳解决方案,但最好添加一些 cmets 来说明您选择此方法的原因以及您认为它有效的原因。 :)【参考方案20】:

很抱歉挖坟,但我一直在与这个问题作斗争一段时间,终于想出了一种方法来处理这些多次触发的事件。我要感谢这个帖子中的每个人,因为我在解决这个问题时在许多参考资料中都使用过它。

这是我的完整代码。它使用字典来跟踪文件最后一次写入的日期和时间。它比较该值,如果相同,则抑制事件。然后在启动新线程后设置值。

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

【讨论】:

不起作用,因为触发的事件相隔一段时间:上次写入时间:636076274162565607 上次写入时间:636076274162655722【参考方案21】:

如果没有询问事件,很遗憾没有现成的 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

【讨论】:

【参考方案22】:

在我的情况下,一旦插入完成,就需要获取由其他应用程序插入的文本文件的最后一行。这是我的解决方案。当第一个事件被引发时,我禁止观察者引发其他事件,然后我调用计时器 TimeElapsedEvent 因为当我的句柄函数 OnChanged 被调用时,我需要文本文件的大小,但当时的大小不是实际大小,它是插入之前的文件大小。所以我等了一会儿继续正确的文件大小。

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;

【讨论】:

【参考方案23】:

我只想对最后一个事件做出反应,以防万一,在 linux 文件更改时,第一次调用时文件似乎是空的,然后在下一次调用时再次填充,不介意浪费一些时间如果操作系统决定进行一些文件/属性更改。

我在这里使用 .NET 异步来帮助我进行线程处理。

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

【讨论】:

【参考方案24】:

这是另一种方法。与其传播快速连续事件中的第一个事件并抑制随后的所有事件,现在所有事件都被抑制,除了最后一个事件。我认为可以从这种方法中受益的场景更为常见。

要做到这一点,我们必须使用滑动延迟。每个传入事件都会取消将触发前一个事件的计时器,并重新启动计时器。这开启了永无止境的一系列事件将永远延迟传播的可能性。为简单起见,下面的扩展方法中并没有针对这种异常情况的规定。

public static class FileSystemWatcherExtensions

    public static IDisposable OnAnyEvent(this FileSystemWatcher source,
        WatcherChangeTypes changeTypes, FileSystemEventHandler handler, int delay)
    
        var cancellations = new Dictionary<string, CancellationTokenSource>(
            StringComparer.OrdinalIgnoreCase);
        var locker = new object();
        if (changeTypes.HasFlag(WatcherChangeTypes.Created))
            source.Created += FileSystemWatcher_Event;
        if (changeTypes.HasFlag(WatcherChangeTypes.Deleted))
            source.Deleted += FileSystemWatcher_Event;
        if (changeTypes.HasFlag(WatcherChangeTypes.Changed))
            source.Changed += FileSystemWatcher_Event;
        if (changeTypes.HasFlag(WatcherChangeTypes.Renamed))
            source.Renamed += FileSystemWatcher_Event;
        return new Disposable(() =>
        
            source.Created -= FileSystemWatcher_Event;
            source.Deleted -= FileSystemWatcher_Event;
            source.Changed -= FileSystemWatcher_Event;
            source.Renamed -= FileSystemWatcher_Event;
        );

        async void FileSystemWatcher_Event(object sender, FileSystemEventArgs e)
        
            var key = e.FullPath;
            var cts = new CancellationTokenSource();
            lock (locker)
            
                if (cancellations.TryGetValue(key, out var existing))
                
                    existing.Cancel();
                
                cancellations[key] = cts;
            
            try
            
                await Task.Delay(delay, cts.Token);
                // Omitting ConfigureAwait(false) is intentional here.
                // Continuing in the captured context is desirable.
            
            catch (TaskCanceledException)
            
                return;
            
            lock (locker)
            
                if (cancellations.TryGetValue(key, out var existing)
                    && existing == cts)
                
                    cancellations.Remove(key);
                
            
            cts.Dispose();
            handler(sender, e);
        
    

    public static IDisposable OnAllEvents(this FileSystemWatcher source,
        FileSystemEventHandler handler, int delay)
        => OnAnyEvent(source, WatcherChangeTypes.All, handler, delay);

    public static IDisposable OnCreated(this FileSystemWatcher source,
        FileSystemEventHandler handler, int delay)
        => OnAnyEvent(source, WatcherChangeTypes.Created, handler, delay);

    public static IDisposable OnDeleted(this FileSystemWatcher source,
        FileSystemEventHandler handler, int delay)
        => OnAnyEvent(source, WatcherChangeTypes.Deleted, handler, delay);

    public static IDisposable OnChanged(this FileSystemWatcher source,
        FileSystemEventHandler handler, int delay)
        => OnAnyEvent(source, WatcherChangeTypes.Changed, handler, delay);

    public static IDisposable OnRenamed(this FileSystemWatcher source,
        FileSystemEventHandler handler, int delay)
        => OnAnyEvent(source, WatcherChangeTypes.Renamed, handler, delay);

    private struct Disposable : IDisposable
    
        private readonly Action _action;
        internal Disposable(Action action) => _action = action;
        public void Dispose() => _action?.Invoke();
    

使用示例:

myWatcher.OnAnyEvent(WatcherChangeTypes.Created | WatcherChangeTypes.Changed,
    MyFileSystemWatcher_Event, 100);

这一行结合了两个事件的订阅,CreatedChanged。所以大致相当于这些:

myWatcher.Created += MyFileSystemWatcher_Event;
myWatcher.Changed += MyFileSystemWatcher_Event;

不同之处在于这两个事件被视为单一类型的事件,如果这些事件快速连续,则只会传播最后一个事件。例如,如果Created 事件后跟两个Changed 事件,并且这三个事件之间没有大于100 毫秒的时间间隔,则只有第二个Changed 事件将通过调用MyFileSystemWatcher_Event 处理程序传播,并且之前的将被丢弃。

【讨论】:

【参考方案25】:

我认为解决该问题的最佳解决方案是使用响应式扩展 当你将 event 转换为 observable 时,你只需添加 Throttling(..) (原名为 Debounce(..))

这里是示例代码

        var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
        
            NotifyFilter = NotifyFilters.LastWrite,
            IncludeSubdirectories = true
        ;

        templatesWatcher.EnableRaisingEvents = true;

        Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                addHandler => templatesWatcher.Changed += addHandler,
                removeHandler => templatesWatcher.Changed -= removeHandler)
            .Throttle(TimeSpan.FromSeconds(5))
            .Subscribe(args =>
            
                _logger.LogInformation($"Template file args.EventArgs.Name has changed");
                //TODO do something
            );

【讨论】:

【参考方案26】:

我改变了监视目录中文件的方式。我没有使用 FileSystemWatcher,而是在另一个线程上轮询位置,然后查看文件的 LastWriteTime。

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

使用此信息并保留文件路径的索引及其最新写入时间,我可以确定已更改或已在特定位置创建的文件。这使我摆脱了 FileSystemWatcher 的怪异之处。主要缺点是您需要一个数据结构来存储 LastWriteTime 和对文件的引用,但它可靠且易于实现。

【讨论】:

而且你必须烧掉后台循环而不是被系统事件通知。【参考方案27】:

我可以通过添加一个检查缓冲区数组中的重复项的函数来做到这一点。

然后使用计时器在 X 时间未修改数组后执行操作: - 每次向缓冲区写入内容时重置计时器 - 在滴答声上执行操作

这也捕获了另一种重复类型。如果您修改文件夹内的文件,该文件夹也会引发 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

【讨论】:

【参考方案28】:

此解决方案在生产应用程序中对我有用:

环境:

VB.Net 框架 4.5.2

手动设置对象属性:NotifyFilter = Size

然后使用这个代码:

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

【讨论】:

它使用与@Jamie Krcmar 相同的概念,但用于 VB.NET【参考方案29】:

试试这个!

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.



【讨论】:

【参考方案30】:

我必须结合上面帖子中的几个想法并添加文件锁定检查以使其对我有用:

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
    

【讨论】:

以上是关于FileSystemWatcher Changed 事件引发两次的主要内容,如果未能解决你的问题,请参考以下文章

System.IO.FileSystemWatcher

FileSystemWatcher 有一个奇怪的行为

C#(098):文件监视 FileSystemWatcher

使用filesystemwatcher时怎么处理大文件

FileSystemWatcher 无法正常工作

FileSystemWatcher - 如何使用它?