2010-04-20 38 views
17

长期以来一直困扰着我的关于FileSystemWatcher的事情之一就是它针对文件的单个逻辑更改触发多个事件。我知道它为什么会发生,但我不想在意 - 我只想重新分析文件一次,而不是连续4-6次。理想情况下,只有在给定文件完成更改时才会触发事件,而不是每一步。无功扩展与FileSystemWatcher

多年来,我已经想出了不同程度的丑陋这个问题的各种解决方案。我认为Reactive Extensions是最终的解决方案,但是我做得不对,我希望有人能指出我的错误。

我有一个扩展方法:

public static IObservable<IEvent<FileSystemEventArgs>> GetChanged(this FileSystemWatcher that) 
{ 
    return Observable.FromEvent<FileSystemEventArgs>(that, "Changed"); 
} 

最后,我想,让每名一个事件,在给定的时间内 - 这样在一个单一的文件名四个连胜事件被减少到一个事件,但是如果多个文件同时被修改,我不会失去任何东西。 BufferWithTime听起来像是理想的解决方案。

var bufferedChange = watcher.GetChanged() 
    .Select(e => e.EventArgs.FullPath) 
    .BufferWithTime(TimeSpan.FromSeconds(1)) 
    .Where(e => e.Count > 0) 
    .Select(e => e.Distinct()); 

当我订阅了这个观察到,被监视文件中的单个变化触发我的订阅方法一排,其中相当失败的目的四倍。如果我删除Distinct()的呼叫,我会发现四个呼叫中的每一个都包含两个相同的事件 - 所以有一些缓冲正在进行。增加TimeSpan传递给BufferWithTime似乎没有影响 - 我在20秒内没有任何行为改变。

这是我第一次进入Rx,因此我可能错过了一些明显的东西。我做错了吗?有更好的方法吗?感谢您的任何建议...

+1

你能把它包装在一个完整的程序中吗?我有兴趣研究它... – 2010-04-20 19:28:48

+0

是的,我会做一个孤立的测试用例。现在我想到了,我有不止一个观察者处理多个文件夹,并且我需要证明它不是四个不同的观察者以某种方式接收同一对事件。 – 2010-04-20 21:13:30

回答

3

我的错误。不知何故,我有多个FileSystemWatchers监控彼此的文件夹。可观察到的是每个观察者触发一次,但BufferWithTime似乎工作正常。我仍然需要弄清为什么我的观察者正在为我认为他们被配置为忽略的文件夹发射事件,但这与Rx或此问题无关。

事实上,也许我能踢上这个问题,并切换到具有单个观察者监控父文件夹,使用的Rx过滤掉从文件夹中的事件,我不感兴趣的内容。

+0

工程很棒。观察者越少越好。我开始非常喜欢Rx。 – 2010-04-20 23:26:22

3

BufferWithTime.Where( )。选择(...)将做​​的工作,但你真正想要的是Throttle()

+0

我看了'Throttle()',但我不确定它会在这种情况下工作。假设我在同一秒内在他们的三个文件中获得了12个事件 - 我能否确定Throttle将通过这12个事件中的正确三个? [文档](http://goo.gl/rzg2)没有太大的帮助。 – 2010-04-22 03:06:13

+0

我想如果我在节流之前选择了我感兴趣的文件名,我不必担心IEvent 的不同实例如何实现相等性,这是我主要关注的Throttle。 – 2010-04-22 03:16:13

+0

Ahh - 我误解了 – 2010-04-22 04:10:10

9

只是热身一个老话题,因为我的工作,现在,太:

当然在观看一个文件的上下文中,这个主题是微不足道的,因为FileSystemWatcher仅在单个文件的Changed事件发生时每3秒触发一次您通过

_fileSystemWatcher.NotifyFilter = NotifyFilters.Size | .... 

追踪大小,但让我们假设FileSystemWatcher的会开除许多事件在一排(也许很多文件被更改/重命名/创建),和其他人阅读此:

你不想在这种情况下使用Throttle或BufferWithTime: Throttle有点误导。它禁止任何发射,直到TimeSpan时间过去没有事件。含义:当你使用类似Throttle(TimeSpan.FromMilliseconds(200))的东西时,它永远不会触发,并且在每个事件之后都会有一个暂停< 200毫秒。所以这不是人们期望的“节流”。当你想等到用户停止输入内容时,这对用户输入很有用。这对加载节流是不利的。

BufferWithTime也不是你想要的:它只是填充时间缓冲器。当每个事件的初始负载很高时,比如打开与web服务的连接,这很好。在这种情况下,您希望在每“时间”秒内批处理事件。但是,当负载平衡时,因为事件数量没有改变。

解决方案是Sample(TimeSpan time)方法:它采用TimeSpan中的最后一个事件,即“真正的”Throttle。我认为在这种情况下,Rx的人真的搞砸了命名。

+0

感谢您对此问题进行了热烈的讨论,我一直将示例代码保存为该问题的方便之处,并且困扰于查看,并且在我的代码中使用了.Sample。我很在意知道,如果观察文件大小,可以保证在文件更改而不更改文件大小的情况下,您将获得一个事件? – 2012-01-11 23:15:51

+1

@DavidGrenier尝试了它,它只在这种情况下触发NotifyFilter.LastWrite。但这是标准过滤器的一部分,即LastWrite | FileName | DirectoryName,因此您必须手动添加“观看大小更改”。所以我至少使用NF.LastWrite | NF.Size – hko 2012-01-12 23:16:12

4

您可以使用group by来为每个文件名聚合文件系统事件,并使用带Throttle扩展方法的结果观察值。我用整数写了一个小样本,但基本的想法是一样的。

var obs = from n in Enumerable.Range(1, 40).ToObservable() 
    group n by n/10 into g 
    select new { g.Key, Obs = g.Throttle(TimeSpan.FromMilliseconds(10.0)) } into h 
    from x in h.Obs 
    select x; 
obs.Subscribe(x => Console.WriteLine(x)); 

输出:

9 
19 
29 
39 
40 

其是为每个组(n/10)最后观察到的整数。