FileSystemWatcher Changed事件引发两次


335

我有一个要在其中查找文本文件的应用程序,如果对该文件进行了任何更改,我将使用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
@BrettRigby:怪不得。这些潜在的答案都不提供解决问题的方法。它们都是特定问题的解决方法。实际上,它们都没有解决我的特定问题(我必须承认,我还没有测试所有它们)。

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

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

2
我创建了一个类,可以帮助您仅获得一个事件。您可以从github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Answers:


276

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

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

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

不久前,我在页面上加了一些FileSystemWatcher技巧的书签。您可能需要检查一下。



151

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

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

14
我尝试了一下,如果我一次修改一个文件,但是一次修改两个文件(例如,将1.txt和2.txt复制到1.txt和2.txt的副本)会起作用。一个事件不是预期的两个。
Christopher Painter

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

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

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

2
@cYounes:除非你做你的东西是异步的。
David Brabant 2014年

107

通过检查相关文件上的时间戳,可以检测到并丢弃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
}

13
我喜欢该解决方案,但是我使用Rx来做“正确的”事情(更改"Rename"为您感兴趣的事件的名称):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski 2014年

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

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

@PeterJamsmenson事件不会完全同时触发。例如,在保存对磁盘的修改时,记事本可能会生成多个事件,但是在记事本需要执行的几个保存步骤中,这些事件是一个接一个地依次触发的。Babu的方法效果很好。
罗兰

10
不能执行,因为引发的事件彼此分开:上次写入时间:636076274162565607上次写入时间:636076274162655722
Asheh'Aug

23

这是我的解决方案,帮助我阻止了两次活动的发起:

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

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


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

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

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

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

8

我的情况是我有一台装有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

这将精确到秒,但如果两次更改之间的时间间隔足够长到可以通过一秒,它将失败。此外,如果您希望获得更高的准确性,可以使用它,ToString("o")但要为更多的失败做好准备。
2013年

5
不要比较字符串,请使用DateTime.Equals()
Phillip Kamikaze 2014年

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

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

1
这是最好的解决方案,使用信号量而不是计时器。
亚伦·布伦库什

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

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

这是我用于在邮件中以附件形式发送文件的项目中解决此问题的解决方案。即使使用较小的计时器间隔,它也很容易避免发生两次触发事件,但是在我的情况下,1000没问题,因为我更乐意丢失一些更改,而不是每秒以大于1条消息的速度充斥邮箱。至少在完全同时更改多个文件的情况下,它工作得很好。

我想到的另一种解决方案是用字典映射文件将列表替换为它们各自的MD5,因此您不必选择任意间隔,因为您不必删除条目而是更新其值,并且如果您的内容没有更改,请取消。它的缺点是,随着对文件的监视和消耗越来越多的内存,词典会在内存中增长,但是我读到某个地方,受监视的文件量取决于FSW的内部缓冲区,因此可能并不那么重要。Dunno,小心MD5的计算时间将如何影响代码的性能?


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

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

您的解决方案不是线程安全的。在_changedFiles从多个线程访问。解决它的一种方法是使用ConcurrentDictionary而不是List。另一种方式是当前分配FormTimer.SynchronizingObject财产,以及对FileSystemWatcher.SynchronizingObject财产。
Theodor Zoulias

5

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

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

然后将其用作正常值,FileSystemWatcher并监视事件何时触发。

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

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

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

_consolidationInterval似乎对我有用。我希望有人将其分叉并制成NuGet包。
zumalifeguard '18

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

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

4

我知道这是一个老问题,但是有同样的问题,上述解决方案都无法解决我所遇到的问题。我创建了一个字典,该字典将文件名与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);
DiamondDrake '16

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

3

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

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年

3

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

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

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

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

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

3

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

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

1
您不需要调用委托类型的构造函数。 this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;应该做正确的事。
bartonjs

@bartonjs谢谢你。我不确定为什么要调用整个构造函数。老实说,这很可能是新手的错误。无论如何,尽管我似乎对修复程序进行了很好的调试。
Fancy_Mammoth

2

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

        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

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

2

这段代码对我有用。

        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

主要是为了未来我:)

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

1

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

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

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


9
以及您必须消耗后台循环,而不是通过系统事件来通知。
马修·怀特(

1

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

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

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


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

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

1

抱歉,我对此进行了认真的探讨,但现在我已经在这个问题上进行了一段时间的斗争,终于找到了处理这些多次触发事件的方法。我要感谢这个线程中的每个人,因为我在解决此问题时已在许多参考资料中使用了它。

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

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
编程教授

1

如果不提出要求,那就很遗憾,没有针对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

1

就我而言,插入完成后,需要获取其他应用程序插入的文本文件的最后一行。这是我的解决方案。引发第一个事件时,我禁止观察者引发其他事件,然后调用计时器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;
}

1

试试这个,一切正常

  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

我只想对最后一个事件做出反应,以防万一,在linux文件更改时,似乎该文件在第一次调用时为空,然后在下一次调用时再次填充,并且不介意浪费一些时间以防OS决定进行一些文件/属性更改。

我在这里使用.NET async来帮助我做线程。

    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

我认为解决此问题的最佳解决方案是使用反应性扩展当将事件转换为可观察的时,您可以仅添加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
            });

0

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

然后使用计时器在数组未修改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

0

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

环境:

VB.Net Framework 4.5.2

手动设置对象属性:NotifyFilter =大小

然后使用以下代码:

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

0

尝试这个!

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

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

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

我这样处理了双重创建问题,它忽略了第一个事件:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.