views:

299

answers:

2

One of the things that has long bugged me about the FileSystemWatcher is the way it fires multiple events for a single logical change to a file. I know why it happens, but I don't want to have to care - I just want to reparse the file once, not 4-6 times in a row. Ideally, there would be an event that only fires when a given file is done changing, rather than every step along the way.

Over the years I've come up with various solutions to this problem, of varying degrees of ugliness. I thought Reactive Extensions would be the ultimate solution, but there's something I'm not doing right, and I'm hoping someone can point out my mistake.

I have an extension method:

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

Ultimately, I would like to get one event per filename, within a given time period - so that four events in a row with a single filename are reduced to one event, but I don't lose anything if multiple files are modified at the same time. BufferWithTime sounds like the ideal solution.

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

When I subscribe to this observable, a single change to a monitored file triggers my subscription method four times in a row, which rather defeats the purpose. If I remove the Distinct() call, I see that each of the four calls contains two identical events - so there is some buffering going on. Increasing the TimeSpan passed to BufferWithTime seems to have no effect - I went as high as 20 seconds without any change in behavior.

This is my first foray into Rx, so I'm probably missing something obvious. Am I doing it wrong? Is there a better approach? Thanks for any suggestions...

A: 

My mistake. Somehow I've got multiple FileSystemWatchers monitoring each other's folders. The observable was triggering once for each watcher, but BufferWithTime appears to be working correctly. I still need to figure out why my watchers are firing events for folders I thought they were configured to ignore, but that's got nothing to do with Rx or this question.

In fact, maybe I can punt on that problem, and switch to having a single watcher monitoring a parent folder, using Rx to filter out events from folders I'm not interested in.

Joel Mueller
Works great. Fewer watchers is better. I'm starting to really like Rx.
Joel Mueller
+1  A: 

BufferWithTime.Where().Select(...) will do the job, but what you really want is Throttle()

Scott Weinstein
I looked at `Throttle()`, but I wasn't confident that it would work in this case. Say I get 12 events with three filenames in them in the same second - can I be certain that Throttle will let through the right three of those twelve events? [The documentation](http://goo.gl/rzg2) isn't much help.
Joel Mueller
I suppose if I select out the filename I'm interested in before throttling, I don't have to worry about how different instances of `IEvent<FileSystemEventArgs>` implement equality, which was my main concern with Throttle.
Joel Mueller
Ahh - I misunderstood
Scott Weinstein
No, Throttle is great with FileSystemWatcher when you're only watching one file - you don't have to care what the values in the event are, you just care when the events stop coming. It's just in this case I'm watching multiple files, and I need to know which ones changed.
Joel Mueller