Introduction
In my blog post about marshalling calls, I discussed the basic pattern I use to create an asynchronous component. Well, in this article I’ll discuss a specific example. I was inspired by a post to a forum where someone was asking about the simplest of all possible things: searching the file system. He mentioned that he’d be searching the C:\ drive and all subdirectories, and I pointed out that such a search could take a while. I suggested that in such a situation he run the search in a background thread.
Well, anyway I decided to go and write a component that would do such a background file search for you, and handle all the marshalling to the UI. So, I created the FileSystemSearcher component. I wanted it to be a little more hard-core than the standard .NET file system searching, so this component allows for multiple starting search locations, and multiple search patterns.
Creating the Interface
The first step was pretty easy, I decided on the interface to the control. There are three main properties:
- Locations – This contains the locations from which the search will progress. If the user decides on a recursive search, each one of these locations will be a root directory.
- Search Patterns – This contains a set of search patterns, and file system objects which match any of the patterns will be returned.
- Options – This property contains an enumeration which controls the search options. Basically it boils down to whether to look for files and/or directories, and whether to search recursively in child directories.
Next, I had to consider how the actual search would be performed. I decided that there would be a public method (BeginSearch) which would check that a search could be started, do any preparation work, and then invoke a background thread to do the actual work. The background thread would report on its progress by events marshaled to the context of the thread that called BeginSearch.
First off BeginSearch needed to ensure that there wasn’t already a search in progress. To support this I created a boolean variable which would hold the running state, and also created a lock variable to control access to it. If it wasn’t running, the next thing the BeginSearch needed to do was to capture the thread context. All the events would be marshaled to this context. I accomplished this the same way BackgroundWorker does, by getting an AsyncOperation object from AsyncOperationManager.CreateOperation. Finally, I used a delegate’s BeginInvoke to asynchronously kick off the method that did the actual work: PerformSearch.
Reporting Results
I won’t go into the details of the actual searching, suffice it to say that I handle the directory recursion myself and use Directory.GetFiles and Directory.GetDirectories on each directory. This allowed me to report detailed progress. I created the following events for reporting progress:
- SearchCompleted – Fired when the search has completed.
- SearchError – Fired whenever an error was encountered during the search, but the search continues nevertheless, skipping that directory.
- BeginSearchDirectory – Fired when the search system is about to search a given directory.
- EndSearchDirectory – Fired when the search has finished searching a directory.
- ItemFound – Fired when the search has found a matching item.
Firing each event is a pretty involved process because of the thread marshalling that must occur. I’ll use the ItemFound as an example. The code that started the event firing looks similar to this:
_asyncOperation.Post(new SendOrPostCallback(AsyncItemFound), new DirectoryInfo(directory));
So, it’s using the AsyncOperation that was created in BeginSearch to post the directory information to the AsyncItemFound method. In other words, AsyncItemFound will be called on the UI thread. So let’s have a look at that:
private void AsyncItemFound(object state)
{
OnItemFound((FileSystemInfo)state);
}
Well, that’s easy enough. It’s going to call OnItemFound, which will raise the event. I could have raised the event right here, but then people inheriting from my control would not have an OnItemFound to override.
And OnItemFound is also nice and simple:
protected virtual void OnItemFound(FileSystemInfo item)
{
if (ItemFound != null)
{
ItemFound(this, new FileSystemInfoEventArgs(item));
}
}
I’ve made it virtual so child classes can override it, and I simply fire the ItemFound event.
Stopping the Search
After completing the component and testing it for a bit, I decided to add the ability to stop the search. So I created a method called StopSearch, which like BeginSearch tested the running flag in a thread-safe manner, and if the search was still running, set a stop flag. Then, I just needed to add checks in the search code to test the stop flag and exit if it had been set to true.
Since my search algorithm is fairly fine-grained (one directory at a time), I decided not to bother with putting locking around the stop flag when I tested it during the search. Quite simply, at worst the search might search a couple of extra directories due to a race condition. Such a minor issue didn’t warrant adding the locking code.
Now there was only one snag on the horizon, I wanted to ensure that the StopSearch method would block until such time as the search had actually stopped. This is where ManualResetEvent comes into its own. I went along and created an event that would signal that the search was complete.
Inside the BeginSearch I set the event to non-signalled, so that threads would block if they waited on it. I then set it to signaled in AsyncSearchComplete, allowing blocked threads to continue. Finally, in StopSearch, I put a wait on the event.
Conclusion
So there you go; a brief description of what I did, and hopefully a bit of insight into why I did it. Plus, as an added bonus, you get an asynchronous search component. I haven't tested it too throuroughly I'm sorry to say, so please let me know if you find any bugs.
You can download the source code here.