转帖|使用教程|编辑:龚雪|2014-12-16 10:34:05.000|阅读 580 次
概述:本教程介绍了dtSearch中线程的使用方法,包括释放UI线程、状态更新、跨线程等。
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
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.
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.
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.
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(); } }
All you have to do next is fill out the details of the methods that you want to use. You also have to remove the NotImplementedException from some of the ones you don't want to use. In fact a good start it to replace all of the throw new NotImplementedException statements by return except for the CheckForAbort method which returns one of:
AbortValue.Continue AbortValue.Cancel AbortValue.CancelImmediately
You can use this to check to see if the user has clicked an abort button and stop the search acordingly.
For now just replace the method with:
public AbortValue CheckForAbort() { return AbortValue.Continue; }
When each of the methods is called is obvious from their names and you don't have to use any that you don't need. Let's look at how we could use the ProgressUpdate method to keep the user informed of the situation. Let's simply display the type of update being performed:
public void OnProgressUpdate( SearchProgressInfo info) { Console.WriteLine(info.UpdateType); }
Now all we have to do is create an instance of our class and start the search:
SearchStatus SStatus = new SearchStatus(); SJob1.StatusHandler = SStatus; SJob1.Execute();
Now the search starts and the OnProgressUpdate is called as it progresses. Notice that in this case we are using the UI thread to run the search and print the result on the console.
If you want to make the feedback more user friendly you could pass in a ProgressBar to be updated by the OnProgressUpdate method. First we need to modify the constructor:
private ProgressBar _PB; public SearchStatus(ProgressBar PB) { _PB = PB; }
and then the OnProgressUpdate method:
public void OnProgressUpdate (SearchProgressInfo info) { _PB.PerformStep(); if (_PB.Value >= _PB.Maximum) _PB.Value = _PB.Minimum; }
Now if you run the same program you will see the ProgressBar update as the search progresses.
SearchStatus SStatus = new SearchStatus(progressBar1); SJob1.StatusHandler = SStatus; SJob1.Execute();
There is a small problem here. We are still hogging the UI Thread. It would be better to use ExecuteInThread to run the search on another thread. However this leads to another small problem - cross threading.
If you keep the OnProgressUpdate method unchanged and simply use
SJob1.ExecuteInThread();
The program will crash with an error message
Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on.
The problem is that all of the method of the Status object are run on the thread that is used for the search and .NET enforces the rule that only the thread that created a UI control can access it.
The solution to the problem sounds more involved than it is. All we have to do is to use the control's Invoke method to run a method using the thread that created the control i.e. the UI thread in this case.
Using the Invoke method is generally complicated by the need to create a delegate but in C# 4 this is very much easier because we can make use of lambda expressions. So to make the new version work we simply change the OnProgressUpdate to read:
public void OnProgressUpdate( SearchProgressInfo info) { _PB.Invoke(new Action(() => { _PB.PerformStep(); if (_PB.Value >= _PB.Maximum) _PB.Value = _PB.Minimum; } )); }
The Invoke method runs the code that updates the progress bar on the original UI thread.
You can handle similar cross threading problems using the same sort of technique and decouple the search thread from the UI thread, so keeping everything responsive and under the control of the user - which is what makes a good application.
Building an application around dtSearch is also a matter of what you do with the search results. You can process these as they are produced using the same sorts of techniques discussed in this article. Then there are many other features that we haven't even touched upon - CDsearch, Websearch and setting up the web Spider to name just three, but these are other stories.
原文地址://www.i-programmer.info/programming/database/2887-going-further-with-dtsearch.html
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn
文章转载自:慧都控件网