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

Improper Usage of Invoke

By , 26 Jun 2006
 

Introduction

Asynchronous programming is great. You can easily use the ThreadPool and other threading constructs to do background work while keeping your UI responsive to the user. But, improper use of Invoke() can cause your UI to appear locked up. Here's a short guide on how to not communicate with your UI thread.

Thread Safety and the UI

For performance reasons, MS decided not to make instances of controls thread safe (in fact, practically, no object instances in the framework are thread safe; all static objects and methods are, however). Controls also need access to the message pump (started by Application.Run()) in order to function properly. So, the initial thread that executes when your program runs (check the Main function in program.cs (.NET 2.0) or in your main form (.NET 1.1 and earlier)) and that creates the initial form and starts the message pump is thus referred to as the "UI Thread". It is associated with the message pump and any forms and other controls it creates.

Controls know what thread they were created on, and check to make sure that any calls that manipulate their state are run by this thread. Any attempt to manipulate that state from a thread other than the UI thread will cause the control to throw an InvalidOperationException. To allow for this cross-thread modification of the UI, the Control class offers the InvokeRequired property and the Invoke (and BeginInvoke, etc.) method.

Invoke and InvokeRequired

Invoke (and BeginInvoke, etc) and InvokeRequired are inherited from System.Windows.Forms.Control. That means that you can use them on any Windows Forms control, from a lowly Label all the way up to a Form.

When you check InvokeRequired, the control (Form or otherwise) retrieves the window thread process ID of its parent control (this propagates up to the initial parent, I believe). It then compares this ID with the ID of the calling thread. If they do not match, this method returns true (it may return false when the parent control's handle doesn't exist, but that's a rare occurrence that is covered in the documentation and in my code sample). This tells you that the current thread cannot safely manipulate the control. The solution to this problem is to send a message to the UI thread telling it to execute a method for you. This is what Invoke (and its siblings) does for you.

Using Invoke and InvokeRequired

The Invoke method essentially (looking at the method in Reflector leads me to keep it simple!) posts a message to the UI thread's message pump, asking it to execute the method passed to Invoke via a delegate (function pointer). When this message is read by the UI thread's message pump, the UI thread executes the method pointed at by the passed delegate, which will be run by the UI thread and is therefore free to modify the control. Calling Invoke will block the calling thread until the UI thread has completed its task. Calling BeginInvoke will return immediately so that the calling thread can continue processing while the UI thread services the request asynchronously (not calling EndInvoke will not leak resources in this sole case, so it is safe to ignore this part of the Asynchronous Programming Model).

The Problem with Invoke

Calling Invoke can cause a bottleneck that will result in your program becoming non-responsive, if you're not careful. The following code snippet (from the sample) will demonstrate this:

public Form1()
{
    InitializeComponent();
    //  Invoke does not work without the control
    //  handle first being created. Since I am 
    //  starting the worker threads in the constructor, 
    //  I have to force creation of this handle early.
    //  Remove this code and see what happens!
    while (!IsHandleCreated)
    {
        // force handle creation
        IntPtr temp = Handle;
    }
    ThreadPool.QueueUserWorkItem(Foo);
    ThreadPool.QueueUserWorkItem(Foo);
    ThreadPool.QueueUserWorkItem(Foo);
}
public void Foo(object o)
{
    while (true)
    {
        rtb.Invoke(new StringDelegate(UpdateDisplay), 
            new object[] { "Foo!\n" });
        //Thread.Sleep(10);
    }
}
public void UpdateDisplay(string text)
{
    this.rtb.Text += text;
}

public delegate void StringDelegate(string foo);

Since I know that Foo is only called from worker threads, I don't bother to check InvokeRequired. Notice that I call Handle to explicitly cause the form to create its window handle, without which Invoke will not work (this is usually not needed; the fact that I start the worker threads in the constructor necessitates this).

This code demonstrates that worker threads pounding the message pump has the same effect as running the code synchronously via the UI thread. The message pump becomes clogged by calls to run UpdateDisplay, and the worker threads get blocked while waiting for their Invokes to be serviced. In a situation like this, you would want to add Thread.Sleep to the worker threads, indicating a small amount (like 10ms) to give the CPU time to block the worker thread and allow the UI thread to process some of its messages.

Conclusion

Using Invoke is a quick and easy way to allow your background threads to modify your UI. But, if you aren't careful, you may cause your UI to lock up just as if it were doing all the work in the first place.

License

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

About the Author

William Sullivan
Web Developer
United States United States
Member
Will Sullivan is a full time C# programmer in Columbia, SC. He enjoys sticking his finger through his right nostril and out his left tear duct. In photoshop.

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   
GeneralMy vote of 4memberkeyboarder10 Mar '12 - 15:33 
Great to-the-point hint is explained well.
GeneralMy vote of 5memberstef.tsal21 Nov '11 - 18:02 
helpful
GeneralThis article has no audiencememberPeter Wone27 Jun '06 - 1:47 
Clear, well written and utterly without audience. Anyone likely to understand this article will not need it. Flatlining the CPU with a tight loop that doesn't yield is just plain sloppy.
GeneralRe: This article has no audiencememberWilliam Sullivan27 Jun '06 - 2:50 
Actually, I posted this in response to a question from another CodeProject member, so there's at least an audience of one. Besides that, slamming the UI with messages from worker threads is a common error made by noobs to multithreading. Saw someone at my work do that within the last week, which is another reason why...
GeneralRe: This article has no audiencememberPeter Wone19 Nov '06 - 13:26 
OK, now I see your point. But in that case, it might aid reader comprehension of the nature of the problem to start by explaining this (I know you already know this but if your audience did there'd be no need for the rest of the article) -
 
Windows may be a pre-emptive multi-tasking operating system, with every process guaranteed to get a regular slice of cpu time, but threads are not processes. Within a dotnet process, thread scheduling is managed cooperatively using a message queue, just like 16-bit Windows used to do. So if a thread gets control and then loops without regularly yielding, all the other threads will stall.
 
Another undesirable effect of looping without yielding is that the CPU will flatline and although other processes will not stall completely, they will become very sluggish.
 
A third and very annoying effect is that by stalling a dotnet process's message queue you can (depending on what was incomplete when it stalled) consequentially stall COM based interprocess communication. Microsoft Outlook often does this, and it causes Windows Explorer and the common dialogs (such as File Open) to respond very slowly and sometimes lock up.
 
So, what do we learn? PUMP THE MESSAGE QUEUE. How? Thread.Sleep()
 
PeterW
--------------------
If you can spell and use correct grammar for your compiler, what makes you think I will tolerate less?

GeneralRe: This article has no audiencememberLars Michael7 Nov '06 - 23:16 
I think it is great to see articles like this! Peter, maybe you are so smart, but dont try to figure out what audience articles in here will have.
GeneralRe: This article has no audiencememberPunCha20 Mar '07 - 23:47 
At least, I like the article. He made every thing clear.
GeneralRe: This article has no audiencememberEnglesito25 Apr '07 - 15:32 
I just found it useful in confirming a problem I have. A solution would be even more useful!
 
My problem is related to using a timer to update the gui (a progress bar). It seems at times the computer goes slow (probably due to the background process) and the timer events start pilling up.
 
This is the pattern I'm about to try:
 
private bool _ProcessingTimeout = false;
private object _TimeoutSync = new object();
private void Timout_Event(object state)
{
 // ensure only one at a time and subsequent are ignored. 
 // We had a problem of overloading the gui
 lock (_TimeoutSync) // one timer at a time (if each is a unique thread?)
 {
  if (!ProcessingTimeout) // ignore subsequent
  {
   try
   {
    _ProcessingTimeout = true;
    if (this.InvokeRequired)
    {
     this.Invoke(new System.EventHandler(TimoutSafe_Event), new object[] { state, System.EventArgs.Empty });
    }
   }
   finally
   {
    _ProcessingTimeout = false;
   }
  }
 }
}

GeneralFrequent updates [modified]memberPeter Wone29 Apr '07 - 16:16 
The approach you describe will work but it is very messy.
 
Create a queue by inheriting from Queue and implement enqueue and dequeue with locking and consolidation:
 
public override Enqueue(object obj)
{
  lock (this) if (!Contains(obj)) base.Enqueue(obj);
}
 
public override object Dequeue()
{
  lock (this) return base.Dequeue();
}
 
The locking makes the queue threadsafe so you can use it from any thread without preamble. The consolidation means that messages won't pile up. You will have to define some kind of command class for the messages and implement command.Equals() so that Contains returns true for equivalent instances otherwise you won't get consolidation. You can exploit this to have some messages consolidate and others not.
 
You can then use your timer if you wish to poll the queue but personally I would use a background thread to poll the queue in a loop and use form.Invoke to process the commands. You can use your gating flag to throttle this. Don't forget to sleep the polling thread for a couple of ms each loop iteration otherwise you'll flatline the CPU.
 

-- modified at 18:48 Monday 30th April, 2007
 
A better implementation of consolidation would involve getting a reference to the already enqueued instance of a command so as to update its parameters with values from the most recent request. However, quite a bit more code is entailed since you can't use the framework Queue class as a basis.
 
PeterW
--------------------
If you can spell and use correct grammar for your compiler, what makes you think I will tolerate less?

QuestionCode link ?????memberfwsouthern26 Jun '06 - 14:26 
Defective -- please fix.
AnswerRe: Code link ?????memberWilliam Sullivan27 Jun '06 - 2:50 
Wasn't able to yesterday. Will try again today.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 26 Jun 2006
Article Copyright 2006 by William Sullivan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid