Threading Out Tasks in a C#.NET GUI



Click here for a larger image.

Figure 1: Main Form Interface

Environment: Win2K, WinXP

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 get 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 it’s out there “doing its thing,” the main window now “frozen;” 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.

All right, let’s jump right into the example problem and code. In this example, I’ve created a simple interface with four “gadgets.” Just four 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 seperate 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 able to be used while that 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:

     //
     // buttonWorkInThread click handler
     //
     // This method instantiates the and starts the thread.
     // It also disables the button so the user doens't try to
     // click it again while the thread is running.
     //
     private void buttonWorkInThread_Click(object sender,
                                           System.EventArgs e)
     {
         // The class that has the method we'll use
         TextReverseThread oReverser;

         // The thread object itself
         Thread oStringReverseThread;

         // Instantiage the object
         oReverser = new TextReverseThread(this.oRTB);

         // Instantiate the thread and define the method
         // that will be used when the thread runs
         oStringReverseThread =
             new Thread( new ThreadStart(oReverser.DoReverse) );

         // Give the thread a name
         oStringReverseThread.Name = "STRINGREVERSE_THREAD";

         // disable start thread button
         this.buttonWorkInThread.Enabled = false;
         this.buttonWorkInThread.Text = "Working....";
         this.buttonDoHere.Enabled = false;

         // Start the thread
         oStringReverseThread.Start();
     }

Dissection

  1. 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 that is a seperate class containing the method I want to run in my thread.

  2. Create an inststance of the Thread class itself. This is obviously required for a container to run the thread inside of.

  3. Instantiate the instance of the class we are using for the thead’s run(). Note that I gave the local form’s RichTextBox object as an argument to the constructor..we’ll cover that below.

  4. Next, we have to instantiate the thread and tell him what method will be his run() method. That is, what method will he execute when he is started. We are telling him his run() method is the DoReverse() method of the just instantiated oReverser object.

  5. 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”.

  6. Now, before we officially start him 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.

  7. Everything is in order, start the thread by calling the thread’s start() method.

Now….that thing I mentioned in Step 3 was crucial to 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. Becaus this other class/thread is just created by us (and for this purpose), we have no safety issues to really worry about giving him 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 his work and then update the control when his 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 (such as 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 disfunctional while its processing. Those events won’t be handled until that processing is complete.

Other gotchas to worry about…..

When you start that thread that is going to update a control….you better make sure NOBODY can be trying to play with that control as well. You could have a user udpate 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. You can’t leave that button enabled. What if user clicked it again…now two threads that have access to the same control are running. 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 “upate event” that runs whenever your thread updates the control (in this case, the TextChanged() event of the RichTextBox control).

Let’s see some example code to make that idea more clear.

  private void oRTB_TextChanged(object sender, System.EventArgs e)
  {
      // Re-enable "do in thread" and "Do in form" buttons
      this.buttonWorkInThread.Text = "Do In Thread";
      this.buttonWorkInThread.Enabled = true;

      this.buttonDoHere.Text = "Do Here In This Form";
      this.buttonDoHere.Enabled = true;
  }

There’s nothing extravagent to try to list out in steps. This is the part where we are in the update handler (TextChanged() of the RichTextBox). When we are done, we now can safely re-enable those buttons that allow access to the RichTextBox.

Figure 2: Form While Thread Is Running

Downloads


Download demo project – 27 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read