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.



Click here for a larger image.

Figure 2: Form While Thread Is Running

Downloads

Download demo project - 27 Kb


Comments

  • Clearing the richTextBox periodically

    Posted by goldfish383 on 08/28/2009 01:10am

    Christopher, Very well explained!!! Good job. I have a similar problem with my windows application. I would need to clear my richtextBox every now and then in order to have the control to keep updating messages. Currently, I am checking like this: if(richTextBox.Text.Length >= 960) { richTextBox.Text = ""; }//set small value for simplicity, so I can verify it is really clearing off the data. Unfortunately, my UI continues to freeze even upon checking this condition. Is there any alternative to this?? How is it usually achieved in situation where there is very huge data to display that would cross the MaximumLength?? Thanks

    Reply
  • Good but what about the message queue?

    Posted by Legacy on 06/19/2003 12:00am

    Originally posted by: GeMe Hendrix

    I use to have the same problem in general windows programming. On most cases, it is a general error to think that you have to create a thread to free up the user interface. Instead we can just process the message queue for the window once in a while. Other important UI considerations can be the following...

    - How about changing the cursor to a wait cursor?
    - How about using a progress bar.
    - How about displaying a wait animation.

    These are important UI considerations for a process that could freeze up the UI.

    Threads can be a pain in backside. Especially if you want to reliably shut down your application, share resources between threads etc.

    I still use threads myself, it some situations I don't have a choice. But just don't use them for the sake of it.

    Regards,

    GeMe

    Reply
  • FYI

    Posted by Legacy on 06/17/2003 12:00am

    Originally posted by: Stuart Fujitani

    Chris,

    There are two other threading alternatives you can use: asynchronous delegates and the ThreadPool class. The advantages of using these alternatives is that the CLR will obtain the thread from the thread pool. Applications that utilize many threads will receive a resource and performance benefit from the CLR's reuse of previously allocated threads.

    Also, it is well documented that making calls on a UI control from a thread other than the one that owns it may result in a deadlock or exception. The proper approach is for the worker thread to check if the call to the control must be marshalled to the owning thread. This can be achieved by checking the control's InvokeRequired property. If "true" is returned then you need to call either Invoke() or BeginInvoke() passing it a delegate that is responsible for making the call on the control. The call to the delegate will be marshalled to the UI thread where the delegate can safely make calls on the control.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds