Let's define the problem first. This occurred to me recently when I was writing my first attempt at a (semi) "real" application in Visual C#.NET. I got my application completed (functionality wise that is) and I click on a button. The task that runs when this particular button is clicked can take a long time, depending how much text was in a particular window. Well, when its out there "doing its thing", the main window now "froze", that is, it does not repaint, move or do anything. Other controls on the form that have nothing to do with that task are also unable to be used.
That simply will not do! I need that task to run in its own thread and leave my main window alone in peace, to continue updating and allow other (unrelated) controls to be used. So, now I will present a method (there may be others..even better methods available), but this is the way I solved the problem. By the way, you may be wondering why I am writing this article...although there are many thread examples already out there, most that I found simply did something like a simple console based producer/consumer example, and it really didn't seem to show me how to solve the problem in a GUI. It seems the paradigm is (only slightly) different when trying to do the thread in a GUI. It probably seems different because in the GUI, you want to be able to do other things, as where most console examples just use threads to wait on one another or share common memory elements.
Alright, let's jump right into the example problem and code. In this example, I've created a simple interface with 4 "gadgets". Just 4 things that will "do something" when activated. This example has two buttons up top with a [Do In Thread] button and a [Do Here In This Form] button. Right below that, is a
RichTextBox with a large amount of text. The idea is that when [Do In Thread] is clicked, it will go out and do something that will take a little bit of time, with this large amount of text. While this is happening, I want the other "gadgets" on the form to be active as well as the form itself. So, the text processing (it reverses all the text a lot of times and redisplays it in the rich text box) will run in a separate thread (disabling the [Do In Thread] button (as well as the [Do Here In Form] button when it starts) and the main form and other controls will still be active and be able to be used, while the main task is going on in the background.
Alternatively, just to show the contrast, the same text processing function can be performed by clicking the [Do Here In This Form] button, but this time it will not go out and run the text processing in another thread, but it will do it the "standard" way, right there in the click routine for the [Do Here In This Form] button. You will notice the stark difference. As soon as you click it, nothing else can be done on the form. You cannot move the form, you can not click the other (unrelated) buttons, if you move another window in front of it, the form will not repaint, etc...
First task...and most important. Create and start a thread. See the code below that is run when the [Do In Thread]'s click handler is executed:
private void buttonWorkInThread_Click(object sender, System.EventArgs e)
oReverser = new TextReverseThread(this.oRTB);
new Thread( new ThreadStart(oReverser.DoReverse) );
oStringReverseThread.Name = "STRINGREVERSE_THREAD";
this.buttonWorkInThread.Enabled = false;
this.buttonWorkInThread.Text = "Working....";
this.buttonDoHere.Enabled = false;
- Make an instance of the class that contains the method you wish to be your thread's
run(). This method is kept in another class, probably created exclusively for that thread. In this example, I have a class named
TextReverseThread which is a seperate class containing the method I want to run in my thread.
- Create an instance of the
Thread class itself. This is obviously required for a container to run the thread inside of.
- Instantiate the instance of the class we are using for the thread's
run(). Note that I gave the local form's
RichTextBox object as an argument to the constructor. We'll cover that below.
- Next, we have to instantiate the thread and tell it what method will be its
run() method. That is, what method will it execute when he is started. We are telling it its
run() method is the
DoReverse() method of the just instantiated
- Before starting, we give the thread's name attribute something meaningful. This is not necessary, but if we are doing some deep thread debugging, it helps us to know exactly which thread is which, instead of seeing names like "Thread-1".
- Now, before we officially start it off, let's disable that [Do In Thread] button (as well as the [Do Here In This Form] button), so the user doesn't try to keep kicking it off again.
- Everything is in order, start the thread by calling the thread's
Now....that thing I mentioned in step 3 above was crucial for the way I designed this to work. When the thread spawns off, in order to update whatever it is supposed to update in the main form (in this case the
RichTextBox), it must be able to access it to update/change it. Since this other class/thread is just created by us (and for this purpose), we have no safety issues to really worry about giving it our control (the
RichTextBox). So, the constructor of the
TextReverseThread class is designed to take a
RichTextBox control as an argument. Now, the thread can do its work and then update the control when its work is complete.
Meanwhile.....on the main form's side of the world, we just sent off a thread to do something but now we can come right back and allow other events to take place (like redraw for example). You can see that the other things work in Figure 2 which shows the results of a few of the other gadgets that were pushed, while the thread was running. In the traditional way of just letting the click function handle the long running task, the execution is doing that task, so the events (like redraw) can't be handled... causing the main form to not redraw if you try to move it, or allow any other controls to be worked with (regardless of whether they have anything to do with the long running task or not). To see this click the [Do Here In This Form] button to see it work that way. You will notice, now, that all those things become dis-functional while its processing. Those events won't be handled until that processing is complete.
Other gotchas to worry about...
When you start that thread which is going to update a control, you better make sure NOBODY can be allowed trying to play with that control as well. You could have a user update it, then the thread returns and updates again, blowing away the data the user just entered. So, in this example, I have the
RichTextBox control disabled from the beginning. However, if you wanted this to be enabled, so a user could update it, you would probably want to disable it while that thread is executing.
Another worry...is that button that kicks off a thread. Can't leave that button enabled. What if user clicked it again...no two threads are running which have access to the same control. So you better disable that button when it is clicked as well. Basically...you probably want to disable any controls that are related to what that thread is going to be "touching" (in any way) until the thread completes. You can re-enable whatever controls you had disabled in the "update event" that runs whenever your thread updates the control (in this case, the
TextChanged() event of the
Let's see some example code to make that idea more clear.
private void oRTB_TextChanged(object sender, System.EventArgs e)
this.buttonWorkInThread.Text = "Do In Thread";
this.buttonWorkInThread.Enabled = true;
this.buttonDoHere.Text = "Do Here In This Form";
this.buttonDoHere.Enabled = true;
Nothing extravagant to try list out in steps...this is the part where we are in the update handler (
TextChanged() of the
RichTextBox), when we are done, we can now safely re-enable those buttons which allow access to the
- Date Posted: June 11, 2003