Friday, July 17, 2009

Thread-Safe C# Form Objects & Delegates

In the first post we'll take a look at what it takes to make a thread-safe Windows Form object call by using delegation in C#.  Why?  Well, sometimes tasks need to be set up in the background--either because they take more than a millisecond or because you may want to get on with the next task.  Also, loading large amounts of information into Windows List Boxes and other form display elements can cause ugly delays and displays--it's nice to not interfere with the Form redrawn while the action takes place!

Today's Tip
---------------
If you've decided to handle certain things in a thread--like download a file or process something or scan something--and would like to be able to display Windows Form stuff about the progress or information being found within the thread... here's how to do it.

Problem: You want to avoid use-collisions and other race conditions between threads so your program doesn't explode.  Adding something to a Windows Form object, like a ListView, requires your thread to be able to safely take control of the object's properties and methods.

Solution: You'll need to be able to create what is called a delegate to handle accessing your Form object's methods and properties. A delegate is a function-pointer and as such allows you to set up virtual calls to methods and properties so you can pass the buck a bit if the object is in use.

Full disclosure about Delegates:
http://msdn.microsoft.com/en-us/library/aa288459(VS.71).aspx

Solution, part 2:
You'll want to "invoke" a call up that delegate to an object's methods / properties. This allows you to "ask permission" to use the object. Many Windows Form components have a great little boolean property: "InvokeRequired". You can access it from those Form components that have inherited that neat little property as .InvokeRequired == "true" or "false".

Full disclosure about Invoke and InvokeRequired:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke(VS.71).aspx and
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx

The code looks like this:
// delegate defines our function pointer
delegate void AddItemToListBoxCallback(ref ListBox LB, string item);

// the function, defined below, matches the function pointer
// for return type as well as what we pass to it
private void AddItemToListBox(ref ListBox LB, string item)
{
   if (LB.InvokeRequired)
   {
      AddItemToListBoxCallback d
= new addItemToListBoxCallback(AddItemToListBox);
      this.Invoke(d, new object[] { LB, item });
   }
   else
   {
      LB.Items.Add(item);
   }
}


You can apply this to any Windows Form object that is compatible with the Control method Invoke and of course has the InvokeRequired property. That covers almost all Form objects.

Quick breakdown:
AddItemToListBoxCallback and AddItemToListBox are names for the delegate and it's callback that I just made up; they're now my names for the function pointer

Variables passed to function / pointer: You can pass anything to the function / delegate, just like you would to any function

To use the above delegate you'd do something like this:
private void MyThread()
{
     //... doing stuff ...
     AddItemToListBox(ref myListBoxObject, myStringObject);
}


Keeping in mind that myListBoxObject is a ListBox object on your Form and that myStringObject is some string you defined. That's it for today's tip.

No comments:

Post a Comment