Introduction
When you have a function that takes a long time and it freezes the screen, the user gets nervous. They simply don't like to lose the control. It would give a friendly impression when they can still click on the form while the application is busy with getting the information, for example. When you start implementing asynchronous methods, you will wonder which technique is best suitable for your application. Would you use a Timer
class or a Thread
class or a BackgroundWorker
? This article uses a BackgroundWorker
class to show how easy it is, and where you need to pay attention when using it.
Background
I have tried to compare different methods of implementing Asynchronous Invocation in my weblog. With Microsoft Visual Studio 2005 , I discovered this class which is new to me.
Using the code
I have put extra comments everywhere possible, to make the sample a snippet of code you can use to start your own implementation. The code implements a very simple action, which is finding files in a directory to simulate the time consuming action. This is the place where you need to change. For the rest, the skeleton should be kept the same.
The background worker is able to send an event to the called thread to change the screen appearance. That is very easy, and I thought it would be easy for everyone to discover. But what if that is not enough and you would like to send other messages back to the caller? So I used BeginInvoke
to invoke a call on the caller thread.
I have two delegates used from the sub thread, to call the method implemented in the main thread.
private delegate void PaintMessage(TreeNode root, string childText); private PaintMessage myAddNode = null;
The OnPaontMessage
implements the adding of a newly found file into an existing (given) root. This is called when the sub-thread finds a new item.
private delegate void EnableCancel(bool enabled); private EnableCancel myEnableCancel = null;
The second delegate is to indicate that the thread has been started and that it could be stopped. It is implemented by the main thread to enable the Cancel button.
Starting the action will be done by calling the RunWorkerAsync
method on the worker.
this.backgroundWorker1.RunWorkerAsync(treeView1.Nodes[path]);
When a sub-folder is found, I send a notification to the main thread to create a node for the sub-folder. Then, I use that node to create a file node in it. But the dilemma is that I can not proceed with filling the file, if the main thread didn't get the chance to create the sub folder. Thus, I need to wait and let the main application take the chance to create it. One simple way to do this is the following loop:
// Create a folder node before proceeding IAsyncResult ar = BeginInvoke(myAddNode, new object[] { root, di.Name }); // Update the screen // Wait until the folder has been created before // proceeding with the content of the folde while (!ar.IsCompleted) { Application.DoEvents(); ar.AsyncWaitHandle.WaitOne(-1, false); }
First, I invoke the delegate and I keep the reference to it. I use the reference to see if it has been completed. And in the loop, I let the application take the chance and do its events. Meanwhile, I tell my sub-thread to wait a bit.