Recently
in one WPF project I needed to monitor multiple folders for the
possible
file and folder changes. I remembered in VC++ we had to use
Win32 API to create our own thread data and run it in a different
thread. In .NET world it seems FileSystemWatcher is the only
and reasonable choice, and you don't have to run multiple threads by
yourself. .NET framework will manage the monitoring thread
for you. However, when I started using it, I found
some issues which could eventually affect how and whether you can use
it. I listed some concerns and tips here. We may
reference this
tips, which is some basic stuff you may be
interested in. I may have another file to list the related
code.
1. Some events will be fired
multiple times.
When
you rename a file, you could get several events fired. This
is a
known issue for file watchers. If you process the changes in
the
event handler, you could handle multiple times for one change.
A
good choice is to group the changes together, and then only process
them once. So a Timer may be good in this situation.
I will
discuss Timer in another paragraph.
2. The
monitored folder name change event is not fired.
When I debugged and found there was no events fired when the monitored
folder was renamed, I was really frustrated. Actually,
FileSystemWatcher does catch the event and furthermore changes the
monitored folder to the new folder and starts monitoring the new
folder. But you just cannot catch it. So this is
the design behaviour, but apparent not what I wanted, because I needed
to display the new folder name immediately. So to monitor the
renaming event is a must.
The original thought came from
this thread. The idea is to create another watcher
to monitor the folder's parent folder, and only watch the directory
name renaming event. Meanwhile you can specify a
filter to only watch the sub folder you are interested in. The following
is code to create the parent watcher.
_parentWatcher = new FileSystemWatcher();
_parentWatcher.Path = (Directory.GetParent(_watchedFolder)).FullName;
string filter =
_watchedFolder.Substring(_watchedFolder.LastIndexOf('\\') + 1);
_parentWatcher.Filter = filter;
_parentWatcher.IncludeSubdirectories = false;
_parentWatcher.Error += Watcher_Error;
_parentWatcher.NotifyFilter = NotifyFilters.DirectoryName;
_parentWatcher.EnableRaisingEvents = true;
3.
You cannot rename or delete the monitored folder's parent folders.
Let's
say you are watching folder A. You are OK to change any files
under A, rename folder A, and maybe delete A. But you can not
rename A's parent B, B's parent C, and so on. Check this
thread. When you try to rename it from Windows
Explorer, you will get the following infamous message:
The action can't
be completed because the folder or a file in it is open in another
program
This is ridiculous since no file or folder is opened,
just the folder is monitored. Again this is a design behaviour.
A
workaround here could be you go ahead to watch your entire drive, let's
say C:\ or D:\. As long as this guy doesn't have a parent,
you
don't worry about renaming a parent folder. But this probably
brings performance issue, because you watch many unnecessary changes,
especially for network drives.
4. Use a Timer
to group multiple events
To be honest, I am not a fan of Timers. I always feel Timers
are low classes in the system and not reliable. Maybe I am
wrong, but I just have that feeling. But in some cases where
missing an event is not that critical, Timers still can do the work.
We should notice there are at least 3 timers:
System.Timers.Timer,
System.Threading.Timer, and System.Windows.Threading.DispatcherTimer.
This
thread
discussing Timers may be useful. I used System.Timers.Timer
in
this case. Somebody mentioned we should use DispatcherTimer,
but
it turns out DispatcherTimer behaves differently with Timer.
When
you respond to every event, you stop and restart the timer to wait for
the same period, so this probably can group all changes together and
fire the final change request.
Another thing needs to notice is in the Timer elapsed event, we should
call
_uiDispatcher.BeginInvoke(new Action(() => {
Messenger.Default.Send("StartRefreshing",
"StartRefreshing");
}));
not just simply send the message.
Messenger.Default.Send("StartRefreshing",
"StartRefreshing");
The
difference is the
later will send the message to a background thread but the former will
send the event to the main ui thread. When you want to change
UI
stuff in the responding function, in the later case you will get
The calling thread cannot access this object because a different thread
owns it
Because
in WPF only the main GUI thread can change the GUI elements.
We
can use Dispatcher.Invoke or BeginInvoke to execute a delegate in the
dispatcher thread. In the middle of this work, I wanted to
use
DispatcherTimer, but it turned out the timer is not fired.
Check this
thread and this
thread
to see the possible reason why this timer is not fired. The
dispatcher timer is created in one thread and will only fire events in
that thread and only the dispatcher of that thread can access these
events.
5. Do we need explicit multiple
threads?
Here comes
another concern users normally have, do we need explicitly to put the
file monitor to another thread? Check this
discussion.
The answer is no, because .NET framework will handle it.
The class will create a thread if it needs that. So
unless
necessary, you don't have to create a thread to put the file monitor in
it. This is different with the old-time Win32 way and of
course a nice improvement.