Click here to Skip to main content
15,884,473 members
Articles / Programming Languages / C#

Accessing Windows Forms Controls across Threads

Rate me:
Please Sign up or sign in to vote.
4.77/5 (32 votes)
17 Oct 2011CPOL8 min read 84K   2.6K   52   17
Many articles exist on the web regarding properly handling multiple application threads and accessing data across those threads, but this article will aim to remove much of the ambiguity or inaccurate information found in many other articles.

Introduction

Many articles exist on the web regarding properly handling multiple application threads and accessing data across those threads, but this article will aim to remove much of the ambiguity or inaccurate information found in many other articles.

What is Multi-Threading?

Many programmers and non-programmers alike are mystified by how multi-tasking operating systems are able to perform so many tasks simultaneously. I think it's important to note that, in truth, no processor is capable of doing "two things at once". The way "parallel execution" is achieved is by granting a tiny amount of processing time to each application, allowing each application to process a few lines of code at a time, giving the appearance that they are all running at once. In programming terms, this is achieved through the method of "multi-threading". In this context, each "thread" is a separate set of instructions to be processed in parallel. It's important to understand the concept of what really goes on inside the processor during multi-threaded or "parallel" processing.

How Does Multi-Threading Work?

For the purposes of an example, let's say we have an application that needs to analyze a photograph and try and find what appear to be people's faces. This would require a very complex set of mathematical algorithms to be run against every pixel or colored point within the image. For a large image, this could take a very long time.

Most code statements that perform some kind of data manipulation are "blocking". That is, they prevent the next line of code from executing until they have finished executing. This is called "Synchronous Execution" because each line of code is executed in sequence.

In a multi-threaded environment, child threads can be spawned by existing threads, enabling the parent thread to continue execution while the child thread performs its work. A statement that creates one of these child threads and immediately returns to execute the next line of code is called "Non-blocking". This is called "Asynchronous Execution" because multiple lines of code appear to execute simultaneously.

Let's put it in a metaphor. Imagine your computer's processor is a highway. When there's only one lane, only one car can travel down the road at a time. If a car drives slowly, then every other car behind it is forced to slow down and wait on it. Now, imagine each thread in your application is an additional lane. Now the cars can travel side-by-side, and some can even outrun others. Now think about the freeway being built out of lines of code and the cars represent the execution of that code.

There are many things to consider when your application runs multiple threads including data synchronization and stability. You wouldn't want Thread A assigning a value to a variable and then Thread B changes that value before Thread A is able to use it. This is what's known as a "Race Condition" (really, not just because I'm using a car analogy). Classes which contain code to prevent these race conditions are considered "Thread Safe". Many classes within the .NET framework are thread safe, but many are not.

The most commonly misused set of .NET types with multi-threading are the System.Windows.Forms controls. These classes are not thread-safe, yet time and again you will find code with improper handling of these objects across thread boundaries. Going back to our highway analogy, thread boundaries would be the lines which define each lane. If one car jumps over to another lane without taking the proper steps of using a turn signal and making sure the lane is clear and... CRASH!

Types which are not thread-safe are like cars with only a front windshield and no side windshields. They can't possibly see what the other cars are doing around them, so the other cars have to make sure they follow the proper procedures if they intend to change lanes.

Changing Lanes

Now to get more specific with Windows Forms controls... All Windows Forms controls inherit from a base class called "System.Windows.Forms.Control". Pretty obvious there. This "Control" class exposes a public Boolean property called "InvokeRequired". This property lets you know if it's ok to access the control from the current thread if it returns a false value.

Basically, this property tells you if it's necessary to invoke the required action on the thread that originally created the control, which would be the case if a child thread was trying to access a control created on the main thread or vise-versa.

Fear Not

"Invoking" is a term used to describe when one thread requests another thread to execute a piece of code. This is accomplished through the use of "delegates".

Delegates are a type-safe way to pass a reference to a method between objects. Here's a basic delegate definition:

C#
delegate void ChangeMyTextDelegate(Control ctrl, string text);

This defines a reference to a method with a return type of "void" and takes two parameters: Control and String. Take a look at the following method that tries to update the text of a specified control.

C#
public static void ChangeMyText(Control ctrl, string text);
{
    ctrl.Text = text;
}

This is all well and good, assuming that the thread that calls this method is the same thread that created the control. If not, we risk having our cars crash into one another.

To counter this, we use the Control's "InvokeRequired" property and the delegate we defined a moment ago. The resulting code looks like this:

C#
delegate void ChangeMyTextDelegate(Control ctrl, string text);
public static void ChangeMyText(Control ctrl, string text)
{
    if (ctrl.InvokeRequired)
    {
        ChangeMyTextDelegate del = new ChangeMyTextDelegate(ChangeMyText);
        ctrl.Invoke(del, ctrl, text)
    }
    else
    {
        ctrl.Text = text;
    }
}

The first line here declares the definition for our delegate. Like before, we define it as a method that returns void, and takes two arguments. Then comes our actual method. The first thing this method does is check to see if we're on the correct thread to access the Control object 'ctrl' passed as the first argument. If an invoke is required, we create a new instance of our delegate and point it at the "ChangeMyText" method. Then, we ask the control to execute the code for us by passing the delegate and the arguments the delegate requires. You might have noticed by now that the delegate's signature matches the signature of the method it represents. This must always be true for a delegate to reference a method.

After the control executes this method for us (on its own thread), "InvokeRequired" will evaluate to false allowing the 'else' block to execute and set the 'Text' property of the control to the string specified in the 'text' argument.

Which Statements Are "Blocking"?

As a rule, practically every statement you code will be blocking. The exception to this rule is if you call a method that begins with the word "Begin". This is a typical coding standard which identifies non-blocking method calls. It's a good syntactic rule to follow when writing your own multi-threaded code, especially if you're writing a reusable class library, since it will help maintain constancy if you hand your DLL to someone else to use. Besides the "Invoke" method we used above, the Control class also exposes a "BeginInvoke" method which does exactly the same thing, except it's a non-blocking statement.

Where Do We Go From Here?

Check out the attached file "CrossThreadUI.cs", which is a single file from a larger class library I've written. Not only does it contain a method almost identical to the example above (called "SetText" in the .cs file), but it also exposes a number of other static methods which enable you to set just about any property on any type of Control object, and some even use reflection to validate the object and argument types. It's a useful helper class and a great way to experience some multi-threaded Windows Forms Control access in action.

Points of Interest

Especially be sure to check out the InvokePropertyMethod in the source code. It will use reflection against the provided instance to find the specified property value of that instance and allow you to execute a method of that property's type, all while maintaining thread-safety. There are several methods included in the source code that allow you to work with a control's property's members.

One interesting thing to note, here, that I discovered while writing this code: All of this source can be easily ported to VB.NET by simply making the necessary syntax changes with the exception of the ShowMessageBox method. This is because, while VB.NET does not support interfaces the same way as C# so the System.Windows.Form object in VB.NET does not inherit from a IWin32Window interface as it does in C#. If you are attempting to re-write this code to VB.NET, it will be necessary to change the Type of the first argument for this method to System.Windows.Form in order to get the code to work. It will compile with the current interface designation, but will throw an exception if you attempt to pass an instance of type System.Windows.Form to it.

History

This article was originally posted (by me) on CSharpCorner on April 23rd, 2009. I do realize that the comment about the face-finding being a time-consuming process is probably not true at this point. :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Harris County
United States United States
I have been programming professionally for about 10 years, and playing around in the coding world since I was 9. My education started on a Timex Sinclair 1000 with 64K of RAM, a B&W TV and my grandmother's old tape recorder for a disc drive.

My career has covered database synchronization software, online trade journals, market analysis and prediction engines, GPS tracking and triangulation, and records storage and processing. I now work for my local county, building an online construction permitting system.

In my spare time I work on cars, write music, and attempt to fly. Still haven't managed to miss the ground, though.

Comments and Discussions

 
GeneralMy vote of 5 Pin
GregoryW25-Apr-13 3:45
GregoryW25-Apr-13 3:45 
GeneralRe: My vote of 5 Pin
StyrofoamBoy26-Apr-13 20:17
StyrofoamBoy26-Apr-13 20:17 
GeneralRe: My vote of 5 Pin
GregoryW26-Apr-13 21:41
GregoryW26-Apr-13 21:41 
QuestionGood article, could use some improvements Pin
onelopez18-Dec-12 6:11
onelopez18-Dec-12 6:11 
AnswerRe: Good article, could use some improvements Pin
StyrofoamBoy26-Apr-13 20:15
StyrofoamBoy26-Apr-13 20:15 
GeneralMy vote of 5 Pin
Kanasz Robert27-Sep-12 8:33
professionalKanasz Robert27-Sep-12 8:33 
Questionexcellet Pin
shekexi9-May-12 0:59
shekexi9-May-12 0:59 
GeneralMy vote of 5 Pin
shekexi9-May-12 0:57
shekexi9-May-12 0:57 
GeneralMy vote of 5 Pin
VJ Reddy20-Feb-12 2:59
VJ Reddy20-Feb-12 2:59 
QuestionNice Pin
Pedram Karimi (Original)24-Nov-11 18:54
Pedram Karimi (Original)24-Nov-11 18:54 
GeneralMy vote of 5 Pin
rraacc1725-Oct-11 5:20
rraacc1725-Oct-11 5:20 
QuestionGood but see SafeInvokerExt Pin
Yves24-Oct-11 12:11
Yves24-Oct-11 12:11 
AnswerRe: Good but see SafeInvokerExt Pin
StyrofoamBoy26-Apr-13 20:22
StyrofoamBoy26-Apr-13 20:22 
QuestionThoughts Pin
PIEBALDconsult17-Oct-11 18:18
mvePIEBALDconsult17-Oct-11 18:18 
AnswerRe: Thoughts Pin
StyrofoamBoy26-Apr-13 20:19
StyrofoamBoy26-Apr-13 20:19 
GeneralMy vote of 4 Pin
Sander Rossel17-Oct-11 11:16
professionalSander Rossel17-Oct-11 11:16 
QuestionI will give it a 5 Pin
Petr Kohout17-Oct-11 10:55
Petr Kohout17-Oct-11 10:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.