Multithreading in C#

Multithreading is something that seems to keep popping up on me when I am coding. My applications either need to do some crazy calculation (and not return 42) or a function will be waiting for a while before returning anything. Both of these leave my GUI locked up to the point where in Vista I get a straight black screen and the wait O, and in XP if I click on the GUI I get yelled at for touching an application that is not responding. I soon learned you can't just call all your code out of the GUI, there are these sweet abstractions like…Business Logic Layer, and Data Access layer. But that's not what this post is about. If I get my code away from the GUI but it is still called by say, a button click, the process will still not respond and the user will be unable to interact with the GUI or get any feedback about execution.

The answer to this problem is fairly simple Multithreading! There are a few different ways to go about it. The two I've used the most are what I will talk about, BackgroundWorkers and Threads.

System. ComponentModel.BackgroundWorker

BackgroundWorkers were the easiest way for me to get into multithreading an application, and they are the first way I did. BackgroundWorkers take care of all the busy work behind the scenes for you, you don't even have to know what a thread pool is, or Mutex, or Semaphore, or even apartment states. There are some setbacks though. You can't build a Ferrari with legos.

To get your BackgroundWorker going first create a BackgroundWorker object, this takes no arguments. There are a few events you will need to handle as well. DoWork is called when the BackgroundWorker is started from RunWorkerAsync; DoWork is where the meat of your processing intense code will go. If you need to pass an argument to your thread you can use RunWorkerAsync(object), the drawback is you can only send one object to the worker so you may need to write your own container class. ProgressChanged is called when ReportProgress(int) is called, one drawback of this is you can only represent completion on a 1-100 basis (progress bar) which for some applications makes sense but there is no built in method of text feedback on progress. And lastly you will need to handle RunWorkerCompleted which is called whenever the worker ends. This could be because of an error in the thread, the thread dying naturally or by calling CancelAsync.

BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

In bw_DoWork any arguments passed to the worker are in DoWorkEventArgs.Argument but this example takes no arguments.

void bw_DoWork(object sender, DoWorkEventArgs e)
{
 e.Result = doMassiveWorkOMG();
 return
;

}

ProgreessChanged is simple enough. If I have some metric to determine what point in finishing I am at I can update a progress bar to show the user that progress. Inside the doMassiveWorkOMG function:

while(stillDoingEpicWork)
{
 bw.ReportProgress(percentComplete);
}

 

Simple enough right? So now when doMassiveWorkOMG is done with its work bw_RunWorkerCompleted will be called. Inside bw_RunWorkerCompleted we need to handle and errors that may have occurred (if we care) and handle what happens at the end of worker execution.

 

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
 if
(e.Cancelled)
  throw
new OMGWhyDidYouCancelMeException();
 else
if (e.Error != null)
  throw
e;
 else

 {
  sendResultToSomeplaceAwesome(e.Result);
  giveChildrenOfTheWorldCandy();
 }
}

That's all there is to BackgroundWorkers, fairly simple and gets the job done. Now lets do the same thing in a Thread.

System.Threading.Thread

Threads have a bit more going on, and as such are a little more dificult to use but provides more. Creating threads is a little more dificult than a BackgroundWorker; a Thread takes a ThreadStart object or a ParameterizedThreadStart delegate that links to a void(void) or void(object) function respectivly. So to recreate our previous example we would create our thread like this

Thread myThread = new Thread(ThreadStart(doMassiveWorkOMG));
myThread.Name = "MassiveWorkThread";

I should mention, calling "new Thread()" is discouragedthe favored use is calling ThreadPool.QueueWorkItem.

The name only helps us while in the debugger we can see where the thread is running in the Thread Window. There are plenty of other properties to set for the thread such as Priority which sets the priority of the thread, IsBackground which sets wheather the thread runs as a background process or not, and ApartmentState which lets you set if the ApartmentState of the thread is STA or MTA.

Starting the thread is as simple as BackgroundWorker in that we simply need to call myThread.Start() which calls the function we put in our ThreadStart delegate, In this case doMassiveWorkOMG. Keeping track of progress isnt as simple as a BackgroundWorker but in much the same way we increment a progress bar we can update a status label.

public void updateWorkPercentage(int progress)
{//update the progress bar to a new value
 theBestProgressBar.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
 {
  theBestProgressBar.Value = progress;
 }));
}

public void updateApplicationStatus(string message)
{//update a status label with a new message
 amazingLabel.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate()
 {
  amazingLabel.Content = message;
 }));
}

It is worthwhile to say this method can be used in a BackgroundWorker as well. Take note that I didn't just call label.content or progressBar.value. Since this will be called on a thread that isnt the GUI thread you will end up with a sweet "The calling thread cannot access this object because a different thread owns it." error if you try to call it dirrectly. For an explination of exactly why it must be done this way look here.

Since we don't have a RunWorkerCompleted function or even the ReportProgress function in threads we have to modify the function we call (doMassiveWorkOMG) to handle updating its status, reporting its results (to the GUI/another function/etc)

void doMassiveWorkOMG()
{
 while
(stillDoingEpicWork)
 {
  updateWorkPercentage(percentComplete);
  updateApplicationStatus(currentStatusMessage);
  epicWorkResult = doMoreEpicWork();
 }
 reportWorkResult(epicWorkResult);
}

There are plenty of ways to get values back from the thread which are easily googleable and Im going to leave out here to keep the post under 90 pages.

 

When should I use what!?

Well, I cant tell you that, you know the scope of your application better than I do. But I tend to use BackgroundWorkers when I just need a simple function running in a thread. If I need the ability to pause/resume, access COM objects like the clipboard I use a Thread with an STA Apartment State (BackgroundWorkers cant do COM Interop that I have found).

In the end you can make a BackgroundWorker do almost anything you can make a Thread do with a little work. But I would rather just use the BackgroundWorker as it is and move to Threads if I need anything more.

Published 23 July 2008 02:58 PM by cmsears
Filed under: , ,
Ads by Lake Quincy Media

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required)