Click here to Skip to main content
Click here to Skip to main content

Towards Cleaner Code II, a C# GUI Invoke/Async Helper

By , 1 Oct 2008
 

Introduction - Threading and User Interfaces

When doing GUI development on Windows you must very careful what thread you access a form or control from. Hopefully most Windows Forms developers know this by now, but in the early versions of the framework it could be a sneaky issue. As of .NET 2.0 they were kind enough to immediately cause an exception when a control was accessed from the wrong thread — forcing you to deal with it now instead of trying to figure out why you get exceptions like this 6 weeks after deploying your application:

c-error.png

I wont delve much in to why this is necessary, there are plenty of explanations on the subject — including many articles on CodeProject. The short story is that in .NET there is still a message loop that is running on the thread your form was run from, access from any other thread just isn’t thread-safe — you can’t use locking because it would block the user interface.

I'm a big fan of using threading in order to keep a User Interface responsive during long tasks and this issue comes up often. Let’s append the Async helper static class that we worked on in Part 1, giving it the ability to schedule tasks on the UI thread. This will go even further towards making an app cleaner and more maintainable.

Background

This is the second part in a series finding areas of necessarily messy or repeated code and find ways to make them more elegant and maintainable. We will discuss the old way, the new way with this helper class, and take a really quick look at the code behind it.

The Conventional way of Doing Things

Let's say we want to update a TextBox in our application. Following with tradition I'll show a few ways to accomplish this, and then how we can do better. Here is what we are trying to do, update the text of a TextBox:

private void UpdateText(string myString){
    textBox1.Text = myString;
}

Simple as can be. The problem is when we want to be able to safely run it from anywhere (any thread) in our code. One way is to check if an invoke is required each time we run it. In this case I’m using anonymous methods to keep things as clean as possible.

// ... //

// ask the control if we are on its UI thread
if(textBox1.InvokeRequired){
   //we are not, so an invoke is required.
   textBox1.Invoke(delegate{UpdateText("This is text");});
}else{
   //we are on the UI thread, we can directly modify the control
   UpdateText("This is text");
}

// ... //

A safer way is to build the invoke into the method itself, ensuring we don't mistakenly run it without checking our thread. Note that this time I am using BeginInvoke, the same concept as before, but it does not block — it runs the task asynchronously.

private void SafeUpdateText(string myString){
	if(textBox1.InvokeRequired){
		//Invoke into this same method, but this time on the correct thread.
		textBox1.BeginInvoke(delegate{UpdateText(myString);});
		return; //important, so we don't fall through
	}
	//this only runs if the above check says an invoke is not required.
	textBox1.Text = myString;
}

Hopefully that isn’t too confusing. If our control's InvokeRequired is true we invoke our own method recursively, knowing that it will be on the right thread on the next time through. This isn’t too bad, but it can be a serious pain to make a method for every operation you need to do — once again its more things to remember and maintain, and more bloat for larger applications. In an application I recently refactored there were several dozen methods like this I was able to eliminate.

The Solution - Using the Code

So now let’s fix up our Async class to make this whole process easier. I decided to make a separate method for this, Async.Do had enough overloads as it was and I wanted it to be very clear at a glance whether the task was UI related. I’ll start with usage. Instead of all of the above, we can now do this:

Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);

Once again, something that might have needed a separate method with 5 to 10 lines of code, can know be distilled into a quick single method that takes care of the details for us. Here's the method prototype: public static AsyncRes UI(Dlg d, Control c, bool asynchronous); Let's check to see what those arguments are.

Our first argument is a delegate, same as we've done before — it can be an actual delegate instance, an anonymous method as above, or even a method without a parameter which .NET will silently wrap into a delegate.

The second parameter is the Control we'd like to invoke with. It can be the control you are accessing, our any of it's parent controls/forms. Of course in the method you pass, you can modify as many controls as you like, provided they are all from the same form / UI thread.

Third is a bool that if true, tells the method to use BeginInvoke to make the call non-blocking. Otherwise the caller will block while the UI thread finds time to run your task.

I, For One, Welcome our New UI Method Overloads

We have two overloads with more options, first, here are the prototypes:

// Now with the ability to grab a return value for you
public static AsyncRes UI(DlgR d, bool getRetVal, Control c, bool async);

// And the last one lets you pass a state object that is returned on an EndInvoke,
// as well as control reentrance
public static AsyncRes UI(DlgR d, bool getRetVal, Control c, object state, bool async,
    ReenteranceMode rMode);

Got all that? It should all be pretty straight-forward. The ReentranceMode enum is not as relevant as before because with UI tasks we are typically invoking tasks on to the same UI thread, as opposed to their own thread — so with this class it is not normally possible for two tasks to run in parallel. I’ll demonstrate using the last overload with the option to obtain a return value.

// ... //

AsyncRes result = Async.UI(
	//make sure the delegate/method returns a value:
	delegate { return textBox1.Text; },
	true, //yes, we want to get the return value
	myForm, //the control to invoke on
	null, //the state object, we don't need to track anything.
	true, //invoke asynchronously?
	ReenteranceMode.Allow); //don't worry about thread safety in this case.

// .... do other things ... //

// now make sure the task above has completed.. 
result.AsyncWaitHandle.WaitOne();

//and use the value
Console.WriteLine("The textbox says: " + result.ReturnValue);

// ... //

That’s about as complicated as it gets now; even the most advanced usage of the new class is simpler than invoking the most basic UI tasks without it. The call to WaitOne is only needed because we chose to invoke asynchronously — we need to make sure it has completed or our value might be null. If we had passed a false to the async parameter the call would have blocked and the value would immediately be guaranteed to be there.

The AsyncResult instance that is returned allows you to wait on the task to complete, get the return value of your method or delegate, get statistics like when the task started or ended, and also a reference to the Control you invoked.

Points of Interest

This functionality has been added to the same Async class we worked with, and it makes use of much of the same code: All of our UI methods simply call the Do method we worked with before, which has been expanded slightly. The only other change is adding all of the UI overloads, each one simply calls Do with various parameters.

The project that we are adding onto was designed for async tasks in a non-UI environment, usage is explained in Part 1, and the code is discussed on my blog.

We were able to add quite a bit of functionality to this handy class, with only a few lines of code!

Again, let me know if you have any problems, comments, suggestions, questions, compliments, or threats of bodily harm — I’ll take them in stride. Hopefully this will help keep your Windows Forms apps well refactored and easy to maintain.

License

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

About the Author

Nicholas Brookins
Architect CodeToast.com
United States United States
Member
Nicholas is the Chief Developer at SAM Systems, a software company that specializes in video surveillance, compression, and streaming. We have a full surveillance and compression platform for Windows, with SDK: www.samipvideo.com.
 
Nick is especially interested in high-performance, multi-threaded/parallel code, video processing, cross-platform development, and GUI design. He maintains a blog about software development and business at www.codetoast.com - check there for more detail on articles posted here, and other musings about the technology world.
 
I also play guitar, collect toasters and dead hard drives, and apparently like writing overly formal bio's in the third person.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralIt's nice when you've Button , but what aboutmemberMarceli2823 Nov '08 - 1:49 
the StatusStrip there is no invoke method . I can't solve it ?!?!
GeneralRe: It's nice when you've Button , but what aboutmemberMarceli2823 Nov '08 - 3:33 
ok. I found it. Sorry !
 
but anyway I can't solve another problem.
 
I cannot call the function where I use Invokes things :
 
public void Update(string sMsg)
{ 
if (statusStrip1.InvokeRewuired)
statusStrip1.BeginInvoke((MethodInvoker)delegate {SetState(sMsg);});
return;
}
m_btnState = sMsg;
 
I call this function from another class :
...Update("Hello");...
 
but the compiler throw me an error , I can't use not static methods ?!?!?
 
any idea ? what I did wrong !
 
BR
Marceli
QuestionSafeUpdateText ot UpdateText?memberozbear7 Oct '08 - 12:29 
In the first rewrite of UpdateText where you use SafeUpdateText instead, did you mean to recusively call SafeUpdateText and not Updatetext?
 
Oz
AnswerRe: SafeUpdateText ot UpdateText?memberNicholas Brookins7 Oct '08 - 12:38 
Good Catch! It would work as written if the other method was still around, but you're correct I meant to recursively call SafeUpdateText so all the logic could be kept in one place. I'll fix that in the next update.
GeneralCompiler errormemberW. Kleinschmit4 Oct '08 - 3:40 
mainForm.Invoke(delegate { mainForm.UpdateText(); });
This does not work:
error CS1660: Cannot convert anonymous method to type 'System.Delegate' because it is not a delegate type
What am I doing wrong?
GeneralRe: Compiler errormemberNicholas Brookins6 Oct '08 - 6:31 
.NET is not always intuitive when it comes to the ways it will silently marshal delegate / anon. method types. In this case it is just a matter of adding a cast to explicitly treat the anon. method as a delegate. You can cast it to any matching delegate, the Framework has one built-in called MethodInvoker that works well:
 
mainForm.Invoke((MethodInvoker)delegate { mainForm.UpdateText(); });
In your example there are two conversion needed - anonymous method to a delegate instance, and then to the Abstract System.Delegate base class- which is what their parameter called for. It won't automatically make two implicit conversion like that. I got around this in my class by asking for a delegate type like the MethodInvoker, which I defined as 'Dlg' just for brevity - this way there is only one implicit typecast, and it accepts it without complaint.
 
Similarly you can pass an anon. method to other methods that take other delegate types, but you may need typecasting in other places. For example, when creating a thread, there are two constructors that take delegates and it will not know which one to use, so you must cast the method into one of them for the compiler:
 
Thread t = new Thread((ThreadStart)delegate { Console.WriteLine(""); });
Hope this helps!
GeneralLink typomemberfaulty4 Oct '08 - 0:23 
The link to your "my blog" is pointing to "http://www.codeproject.com/KB/threads/www.codetoast.com/blog/archives/46", my guess it's just typo.
 
Anyway, good article
 

GeneralRe: Link typomemberNicholas Brookins6 Oct '08 - 6:13 
Ah yes - I must have forgotten to put "http://" in the href.
 
The correct link is http://www.codetoast.com/blog/archives/46[^]
 
Thanks!
GeneralBeginInvoke is dangerousmemberJean-Paul Mikkers2 Oct '08 - 10:51 
I'd like to point out that if there's a thread producing lots of BeginInvoke calls, the application will eventually fail. At least with the synchronous Invoke such a thread will be throttled so the GUI can manage the updates.
GeneralRe: BeginInvoke is dangerousmemberNicholas Brookins3 Oct '08 - 2:47 
That is a good item to mention. I have thought of adding functionality in the future for another mode where Aync GUI calls can be combined in a way, similar to how Invalidate works. If you call it 30 times before the control manages to repaint itself, it still only paints once.
 
Thanks for the comment.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 1 Oct 2008
Article Copyright 2008 by Nicholas Brookins
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid