2010-04-20 37 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)最後觀察到的整數。