我在一个正在寻找文本文件的应用程序中,如果对该文件进行了任何更改,我正在使用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被调用了两次。

评论

@BrettRigby:怪不得。这些潜在答案均无法解决问题。它们都是特定问题的解决方法。实际上,它们都没有解决我的特定问题(我必须承认,我还没有测试全部)。

这是一种解决方法,但应根据解决方法的质量来判断。跟踪更改非常有效,而且很简单。 OP正在寻求一种抑制重复事件的方法,这就是下面的响应。 msdn.microsoft.com/zh-cn/library/…说明多个事件可能是由防病毒或其他“复杂的文件系统内容”(听起来像是借口)引起的。

我最近打开了这个问题github.com/Microsoft/dotnet/issues/347

我创建了一个类,可以帮助您仅获得一个事件。您可以从github.com/melenaos/FileSystemSafeWatcher中获取代码

#1 楼

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


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


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

前一段时间,我已将带有一些FileSystemWatcher提示的页面标记为页面。您可能需要检查一下。

评论


Raymond Chen刚刚在博客上发表了一篇文章:为什么在记事本中保存文件会引发多个FindFirstChangeNotification事件?

–科迪·格雷♦
2014年5月21日在12:29

#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的副本),它会起作用一个事件不是预期的两个。

–克里斯托弗画家
11年11月16日在20:34

已经过了几个月,但我认为我最终要做的是让事件调用一种将业务逻辑放入锁语句中的方法。这样,如果我得到了额外的事件,他们会排队等待,直到轮到他们了,因为上一次迭代会处理所有事情,所以他们无所事事。

–克里斯托弗画家
2012年1月13日14:02



这似乎可以解决问题,但不能解决。如果另一个进程进行了更改,则您可能会丢失它们,它似乎起作用的原因是,另一个进程的IO是异步的,并且您禁用了监视,直到完成处理为止,从而与其他事件一起创建了竞争条件出于兴趣。这就是@ChristopherPainter观察他的问题的原因。

– Jf Beaulac
2014年1月10日19:04

-1:如果您感兴趣的其他更改在禁用时发生了怎么办?

– G. Stoynev
2014年5月22日14:51

@cYounes:除非您异步进行操作。

–大卫·布拉本特(David Brabant)
2014-09-30 15:55

#3 楼

通过检查相关文件中的OnChanged时间戳,可以检测到并丢弃FileSystemWatcher中任何重复的File.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
}


评论


我喜欢该解决方案,但是我已经使用Rx来做“正确”的事情(将“重命名”更改为您感兴趣的事件的名称):Observable.FromEventPattern (fileSystemWatcher,“ Renamed”)。选择(e => e.EventArgs).Distinct(e => e.FullPath).Subscribe(onNext);

– Kjellski
2014年10月24日15:55



我想念什么吗?我不知道这将如何工作。从我已经看到的事件同时触发,因此,如果它们都同时进入上述事件,则它们都将在设置lastRead之前开始运行。

– Peter Jamsmenson
2014年11月17日9:35

由于DateTime仅具有毫秒级的分辨率,因此即使将File.GetLastWriteTime替换为DateTime.Now,此方法也可以使用。根据您的情况,您还可以在全局变量中使用a.FullName来检测重复事件。

–罗兰
2015年12月9日在18:26

由于触发的事件彼此分开,因此不起作用:上次写入时间:636076274162565607上次写入时间:636076274162655722

–阿舍
16年8月24日在8:24

如Asheh解释的那样不起作用。这将起作用:if(lastWriteTime.Ticks-lastRead.Ticks> 100000)

–tala9999
19年1月23日在15:16

#4 楼

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

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


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

评论


另外,在记事本中,我创建了一个包含四个字符的文件:abcd。然后,我打开了一个记事本的新实例,并输入了相同的四个字符。我选择了File |另存为并选择相同的文件。该文件是相同的,并且大小和文件名不会更改,因为该文件具有相同的四个字母,因此不会触发。

–琉璃
2012年11月27日20:19



可能会进行真正的更改而不会更改文件的大小,因此该技术在这种情况下将失败。

–Lee Grissom
13年8月7日在1:14

我想这是一个相当普遍的情况,您知道任何有意义的更改都会修改文件的大小(例如,我的情况是在日志文件后面添加)。尽管使用此解决方案的任何人都应该意识到(并记录)该假设,但这正是我所需要的。

–GrandOpener
2014年3月3日在5:23

@GrandOpener:并非总是如此。就我而言,我正在观看文件,其内容仅包含一个0或1字符。

–user4849927
16年8月8日14:22



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


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

评论


我假设您定期清空哈希表。

– ThunderGr
13-10-11在9:42

这将精确到秒,但如果两次更改之间的时间间隔足够长以超过一秒,它将失败。而且,如果您想提高准确性,可以使用ToString(“ o”),但要为更多的失败做好准备。

– Pragmateek
13-10-22在20:21



不要比较字符串,请使用DateTime.Equals()

– Phillip Kamikaze
2014年8月2日在6:08



不,不要他们不平等。就我当前的项目而言,它们之间相差约一毫秒。我使用(newtime-oldtime).TotalMilliseconds <(任意阈值,通常为5ms)。

–Flynn1179
17年11月27日在15:02

#6 楼

这是我的方法:

// 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的内部缓冲区,因此mayb
不知道MD5的计算时间将如何影响代码的性能,请小心= \

评论


您的解决方案对我来说很棒。只是,您忘记了将该文件添加到_changedFiles列表中。代码的第一部分应如下所示:lock(_changedFiles){if(_changedFiles.Contains(e.FullPath)){return; } _changedFiles.Add(e.FullPath); //添加这个! } //做你的事情

– davidthegrey
17-10-24在15:25



我否决了上面的4个答案,并否决了这个答案。您的答案是第一个通过采取LAST事件来做应做的事情,而不是第一个。正如@Jorn所解释的那样,问题在于文件是成批写入的。其他解决方案对我不起作用。

–编码您的生活
19年6月29日在19:45

您的解决方案不是线程安全的。 _changedFiles是从多个线程访问的。解决此问题的一种方法是使用ConcurrentDictionary而不是List。另一种方法是将当前Form分配给Timer.SynchronizingObject属性以及FileSystemWatcher.SynchronizingObject属性。

– Theodor Zoulias
19-09-24在7:52



#7 楼

尝试使用以下代码:

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


评论


这是最好的解决方案,使用信号量而不是计时器。

–亚伦·布伦库什(Aaron Blenkush)
17年1月10日在18:51

什么信号量?我在这里只看到一个布尔变量。此外,主要问题尚未解决:FileSystemEventHandler仍在触发多个事件。这段代码有什么效力? if(let == false){...} else {let = false; }?难以置信的是如何投票,这仅是StackOverflow徽章的问题。

–sɐunıɔןɐqɐp
4月6日14:27



#8 楼

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

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

然后使用它作为正常的FileSystemWatcher并监视事件的触发时间。

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


评论


在目录上引发事件时,这似乎失败。我通过在打开文件之前包装目录检查来使其工作

–山姆
2014年9月2日,12:50

尽管示例中有错别字,但这对我来说似乎是一个可行的解决方案。但是,就我而言,一秒钟之内可以进行许多更新,因此我不得不大幅度降低_consolidationInterval,以免丢失任何更改。虽然10毫秒似乎很好,但是如果我将_consolidationInterval设置为50毫秒,我仍然会丢失大约50%的更新。我仍然必须运行一些测试以找到最合适的值。

–user4849927
16年8月8日在18:33

_consolidationInterval似乎对我有用。我希望有人分叉,并使其成为一个NuGet包。

– zumalifeguard
18年11月11日在5:24

谢谢:)它解决了我的问题。.希望创建和复制的事件将与单个观察者一起正常工作,以很好地解决此问题。 stackoverflow.com/questions/55015132/…

– techno
19 Mar 6 '19在5:51



太好了我已经将它实现到我的项目中,并且它击败了我为打破它所做的每一次尝试。谢谢。

–克里斯特(Christh)
6月10日20:04

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


评论


这是一个可靠的解决方案,但是缺少一行代码。在此处的代码部分中,您应该添加或更新dateTimeDictionary。 dateTimeDictionary [e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);

–钻石龙
16年8月12日在20:16



没有为我工作。我的变更处理程序被调用了两次,并且文件第二次具有不同的时间戳。可能是因为它是一个大文件,并且第一次写入正在进行中。我发现折叠重复事件的计时器效果更好。

–迈克尔
16 Dec 5'在19:17

这比使用某些计时器禁用该事件要好得多,我的朋友KUDOS!

–MLissCetrus
8月14日15:21

#10 楼

一种可能的“破解”是使用例如Reactive Extensions来限制事件,例如:

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

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

watcher.EnableRaisingEvents = true;


在这种情况下,在我的系统上,我将速度限制为50ms ,但更高的值应该更安全。 (就像我说的,它仍然是一个“ hack”)。

评论


我使用了.Distinct(e => e.FullPath),我觉得它更直观。并且您已经恢复了API预期的行为。

– Kjellski
2014年10月24日15:58

#11 楼

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

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
16-09-22在21:43

@bartonjs谢谢你。我不确定为什么要调用整个构造函数。老实说,这很可能是新手的错误。尽管看起来我的修复工作相当不错。

–Fancy_Mammoth
16-09-23在13:06

#12 楼

主要原因是
第一个事件的上次访问时间是当前时间(文件写入或更改时间)。
然后第二个事件是文件的原始上次访问时间。
我用代码解决。 >
        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;


评论


与此答案相同

–让·保罗
2015年6月3日,13:58

#13 楼

我花了大量时间使用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.
}


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


评论


起初我以为这对我有用,但事实并非如此。我遇到的情况是,文件内容有时会被覆盖,而其他时候文件会被删除并重新创建。尽管您的解决方案在文件被覆盖的情况下似乎可以工作,但在重新创建文件的情况下,它并不总是可以工作。在后一种情况下,事件有时会丢失。

–user4849927
16年8月8日在16:26



尝试整理出不同类型的事件并分别处理,我只是提供了一种可能的解决方法。祝好运。

– Xiaoyuvax
17年2月15日在4:19

尽管没有测试,但我不确定这对创建和删除是否有效。由于fireCount ++和if()语句都是原子的,因此不会等待。即使两个触发事件相互竞争。我想肯定还有其他原因引起您的麻烦。 (迷路?你是什么意思?)

– Xiaoyuvax
17-2-15在4:21



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


评论


虽然这可能是解决所提出问题的最佳解决方案,但最好还是添加一些注释,说明为什么选择这种方法以及为什么认为这种方法可行。 :)

– waka
2014年11月19日在7:03

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


评论


在我的一个项目中使用了这个。很棒!

–泰勒·蒙尼(Tyler Montney)
17年1月10日在15:25

由于触发的事件彼此分开,因此不起作用:上次写入时间:636076274162565607上次写入时间:636076274162655722

–编程教授
17年3月21日在23:04

#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文件更改时,似乎在第一次调用时该文件为空,然后在下一次调用时再次填充,并且不介意浪费一些时间以防OS决定进行一些文件/属性更改。

我在这里使用.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事件,而前一个则被丢弃。

评论


优秀的解决方案。感谢和感谢@Theodor Zoulias

–user584018
12月8日下午5:23

#25 楼

我认为解决此问题的最佳解决方案是使用反应性扩展
当您将事件转换为可观察的时,您可以仅添加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和对该文件的引用,但是它可靠且易于实现。

评论


以及您必须消耗后台循环,而不是通过系统事件来通知。

–马修·怀特(Matthew Whited)
09年12月14日在20:40

#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 Framework 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

– wpcoder
17年1月25日在16:58

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