views:

322

answers:

2

Hi All,

I'm trying to update my ObservableCollection as the FileSystemWatcher notifies changes. I know this is not possible because of cross thread operations.
So i would like to get the name of the file created/deleted/renamed when the event is fired and update it in the UI thread once the event is completed, as we do in BackgroundWorker. Can anyone tell me how to do this?

Also tell me where i should define and start this FileSystemWatcher. Currently i've defined it in the MainViewModel.

P.S.: I've seen similar questions in SO, but didn't get a clear picture

Thanks In Advance,
Veer

+2  A: 
public void SomeActionToBeInvokedOnTheMainThread()
{
    if (someControl.Dispatcher.CheckAccess())
    {
        // you can modify the control
    }
    else
    {
        someControl.Dispatcher.Invoke(
            System.Windows.Threading.DispatcherPriority.Normal,
            new Action(SomeActionToBeInvokedOnTheMainThread)
        );
    }
}
Darin Dimitrov
Any suggestion for my second question?
Veer
This code is an **anti-pattern**. It is significantly less efficient and less readable for this purpose than the much simpler: `someControl.Dispatcher.Invoke(DispatcherPriority.Send, new Action(() => { ... code to modify the control goes here ... }))`.
Ray Burns
The reason it is less efficient is that CheckAccess is called three times: Once within the call, once inside the Dispatcher.Invoke, and once within the recursive call. Calling Dispatcher.Invoke directly bypasses all of this. **The only time the `CheckAccess` makes any sense at all** is if 99%+ of the calls will be from the same thread *and* performance is absolutely critical when calls come fromt he same thread (the performance difference in this case is under 1ns, so you'd better have a good reason to clutter up your code with some strange recursion!).
Ray Burns
+1  A: 

I would think the main view model is the right place to define the FileSystemWatcher. As for the threading issues, this is the easy way:

_watcher = new FileSystemWatcher(path);
_watcher.Created += (obj, e) =>
  Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>
  {
    // Code to handle Created event
  };
_watcher.Changed += (obj, e) =>
  Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>
  {
    // Code to handle Changed event
  };
_watcher.Renamed += (obj, e) =>
  Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>
  {
    // Code to handle Renamed event
  };
_watcher.Deleted += (obj, e) =>
  Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>
  {
    // Code to handle Deleted event
  };
// ...
_watcher.EnableRaisingEvents = true;

Each of the "Code to handle" will execute within the UI thread so it can update the ObservableCollection. Note that the FileSystemEventArgs "e" is available within this code.

If you prefer to use separate event handler methods you can call them from the above code or use this convenient shortcut:

var switchThread =
  (FileSystemEventHandler handler) =>
    (object obj, FileSystemEventArgs e) =>
      Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() =>
       handler(obj, e))

_watcher = new FileSystemWatcher(path);
_watcher.Created += switchThread(OnCreated);
_watcher.Changed += switchThread(OnChanged);
_watcher.Deleted += switchThread(OnDeleted);
_watcher.Renamed += switchThread(OnRenamed);
_watcher.EnableRaisingEvents = true;

where OnCreated, OnChanged, OnDeleted, and OnRenamed are normal event handler methods with the normal signature, for example:

void OnChanged(object sender, FileSystemEventArgs e)
{
  // Code to handle Changed event
}

Personally I prefer the first way of doing it because I don't like creating four extra 1-line methods.

Note that your view model will need to know which Dispatcher to call back on. The easiest way to do this is to derive your view model from DispatcherObject, as assumed above. Another way is for the view model's constructor or the method that registers the FileSystemWatcher events to store a copy of Dispatcher.Current in a local field or local variable, then use that for the .BeginInvoke calls.

Also note that you can use exactly the same code in your view code-behind instead of in your view model if you prefer.

Ray Burns
That's cool. I wanna monitor my entire computer and I've created separate FileSystemWatcher object for each drive. Is that OK? or is there a way to monitor all the drives with a single watcher object?
Veer
I don't think there is any way to monitor all the drives with a single `FileSystemWatcher`, but creating one `FileSystemWatcher` per drive is possible. You can monitor an entire drive by using the `FileSystemWatcher.IncludeSubdirectories` property. I have not tested this for performance. Note also that if your FileSystemWatcher's internal buffer overflows you get a reset event, at which point you must re-scan the tree to find what changed.
Ray Burns
Currently I've created one watcher per drive. But as you said, 've to check the buffer overflows...
Veer
Since Dispatcher is a non-static class Dispatcher.BeginInvoke() does not seem to work. Should i create an object of Main Dispatcher and use it?
Veer
No. The code above was assumed to be within an existing object, such as an Application, Window, or UserControl, all of which have a `Dispatcher` property. It sounds to me like you're putting it in a static method. In this case you'll need to access the proper Dispatcher, which can be obtained from any object in your UI through its Dispatcher property. This should probably be passed into the static method.
Ray Burns
The method is inside the MainViewModel Class and is not static
Veer
Your MainViewModel should derive from DispatcherObject so it can automatically remember which thread is the UI thread. Either that, or its contructor should save a copy of Disptacher.Current in a field and use that for the _dispatcher.BeginInvoke calls.
Ray Burns
I updated my answer to include this information.
Ray Burns
Actually i've saved a copy of Dispatcher.Current. That's what i've awkwardly written "object of Main Dispatcher' in my previous comment:) Thanks Ray
Veer