Threading and dtSearch
Written by Ian Elliot   
Wednesday, 17 August 2011
Article Index
Threading and dtSearch
Cross threading

dtSearch is very easy to use but what do you do when a search is taking so long that it blocks the UI thread? Easy, just run the search on another thread! We take a look at how this works and how it interacts with the UI.

In the first part of my close look at the search and indexing system dtSearch, I covered getting started and the basic principles of operation. Although the main conclusion has to be that this is a really easy to use system, there are always considerations about how to do things in a slightly more sophisticated way.

In this article we take a look at how to deal with big searches and the sorts of things you can do with what you find. It is assumed that you already have dtSearch setup and an index ready to search. If you don't know how to do these things then check out Getting started with dtSearch.

Big search

We explored the simplest way to implement a search in the previous article (if this doesn't make sense then make sure to read it first):

SearchJob SJob1 = new SearchJob();
SJob1.IndexesToSearch.Add(
@"C:\path to Index");
SJob1.BooleanConditions="Hello and World";
SJob1.Execute();

The only problem with this approach is that it blocks your program from doing anything else until the search is complete - which is fine as long as this isn't a long time. You can limit the search by setting the TimeoutSeconds property which simply halts the search after the specified number of seconds. You can also limit the number of files returned using the MaxFilesToRetrieve property.

However, even if you do limit the total amount of work to be performed there is still the problem that the search is being performed on the UI thread and while it is going on nothing else can happen. The standard solution to the problem is to run the workload, whatever it is, on another thread. dtSearch makes this very easy by providing an ExecuteInThread() method which starts the search on a new thread. Notice that without this you would have to go to the trouble of creating and managing a thread. Following the call to ExecuteInThread the search starts to run on a new thread and the UI thread continues on its way unimpeded. In other words you call ExecuteInThread and it returns imediately but the search is still going on and the results aren't ready for you to process yet.

This is good because now the UI thread can get on with managing the UI and responding to events etc. but it raises the question of how you detect when the search results are ready? The solution is to use either the IsThreadDone or the IsThreadDoneWait methods. The first returns true if the search is complete and false otherwise. The second returns true at once if the search is complete but then waits for the specified number of milliseconds if it isn't before returning false.

This sounds easy all we have to do is change the standard code a little:

SJob1.ExecuteInThread();
while (!SJob1.IsThreadDone())
{
}

The idea is that we start the search going and then sit in a "tight" loop waiting for it to complete.

This doesn't work.

Freeing the UI thread

If you try this out you will discover that the UI is frozen for the time that the search is going on and hence there is no gain in using a separate thread. The problem is that while a separate thread is use for the search the UI thread is simply kept busy waiting for it!

You might think that changing the loop to

SJob1.ExecuteInThread();
while (!SJob1.IsThreadDoneWait(100))
{
}

would work but no. The reason is exactly the same - the UI thread is still kept busy while the search is going on.

One way of solving the problem if you are using Windows Forms is to make a call to DoEvents so that the UI thread can deal with any events and update the UI.

SJob1.ExecuteInThread();
while (!SJob1.IsThreadDoneWait(10))
{
Application.DoEvents();
}

This works but many programmers don't like using DoEvents. The reason is that it isn't re-entrant. Imagine for a moment that there was an event handler that also had a DoEvents command. What happens if this event gets processed as the result of the first DoEvents? In practice DoEvents isn't as bad as many claim - as long as you limit it's use to one per application.

A better way to free up the UI and one that works with both WPF and Forms is to use a timer to check every so often that the search is complete. So assuming that there is a Timer object available you would do something like:

 SJob1.ExecuteInThread();
timer1.Interval = 100;
timer1.Enabled=true;
return;
}

At this point the routine that starts the search terminates and the UI thread is free to do what it has to. The timer event handler has to process the search results:

private void timer1_Tick(
object sender, EventArgs e)
{
if (!SJob1.IsThreadDone()) return;
timer1.Enabled = false;
do something with results.

This works and its efficient but some programmers don't like the idea of using a Timer to implement an asynchronous handling strategy. There is an alternative and it isn't much more complicated.

Status updates

There is a more organized and comprehensive way to work with the results of a search as they are obtained. The SearchJob object has a StatusHandler property that can be set to an object which has a set of methods that are called as the Search progresses. Using this you can process the files as they are found and you can keep the UI responsive by not hogging the UI thread.

First we need a suitable status handling object. This can be any object that implements the ISearchStatusHandler or the ISearchStatusHandler2 interface. The ISearchStausHandler2 interface is the same as the ISearchStatusHandler with the addition of a single method - OnProgressUpdate - so we might as well explore this verison of the interface.

To use the status mechanism you first need to define a class that inherits from ISearchStatusHandler2 and you also might as well use the autogenerate option (right click on the interface name) to populate the class with stub methods:

class SearchStatus : ISearchStatusHandler2
{
public void OnProgressUpdate(
SearchProgressInfo info)
{
throw new NotImplementedException();
}

public AbortValue CheckForAbort()
{
throw new NotImplementedException();
}

public void OnFound(
SearchResultsItem item)
{
throw new NotImplementedException();
}

public void OnSearchingFile(
string filename)
{
throw new NotImplementedException();
}

public void OnSearchingIndex(
string index)
{
throw new NotImplementedException();
}
}

Banner


Last Updated ( Friday, 19 August 2011 )