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

Improper Usage of Invoke

, 26 Jun 2006
Rate this:
Please Sign up or sign in to vote.
Asynchronous programming is great, but improper use can cause your form to become non-responsive.

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
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.

Comments and Discussions

 
GeneralMy vote of 4 Pinmemberkeyboarder10-Mar-12 15:33 
GeneralMy vote of 5 Pinmemberstef.tsal21-Nov-11 18:02 
GeneralThis article has no audience PinmemberPeter Wone27-Jun-06 1:47 
GeneralRe: This article has no audience PinmemberWilliam Sullivan27-Jun-06 2:50 
GeneralRe: This article has no audience PinmemberPeter Wone19-Nov-06 13:26 
GeneralRe: This article has no audience PinmemberLars Michael7-Nov-06 23:16 
GeneralRe: This article has no audience PinmemberPunCha20-Mar-07 23:47 
GeneralRe: This article has no audience PinmemberEnglesito25-Apr-07 15:32 
GeneralFrequent updates [modified] PinmemberPeter Wone29-Apr-07 16:16 
QuestionCode link ????? Pinmemberfwsouthern26-Jun-06 14:26 
AnswerRe: Code link ????? PinmemberWilliam Sullivan27-Jun-06 2:50 

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

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

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