1

I am having an issue with Control.BeginInvoke() that does not happen every time. It seems to work well for 5 times or so, then becomes more of a 50/50 success rate after that.

What is happening is that I will call BeginInvoke() on a control that has already been created and showing, and the call will not be executed on the GUI thread immediately. The invoking thread does immediately return from BeginInvoke() and go on about its business. The delegate is not executed on the GUI thread until another BeginInvoke() is executed on the control. Then it is as if it realizes it has something to do and finally does it. This can be over 10 minutes later.

Code

Here is the code that executes the BeginInvoke() that does not execute the delegate immediately:

protected void DisplayPrompt(PromptData promptData)
{
    Debug.WriteLine(DateTime.Now + ": Entering DisplayPrompt: " + Thread.CurrentThread.Name);
    this.CurrentPrompt = promptData;

    if (this.InvokeRequired)
    {
        Debug.WriteLine(DateTime.Now + ": Before BeginInvoke of DisplayPrompt: " + Thread.CurrentThread.Name);
        this.BeginInvoke(new Action(() => this.DisplayPrompt(promptData)));
        Debug.WriteLine(DateTime.Now + ": After BeginInvoke of DisplayPrompt: " + Thread.CurrentThread.Name);
        return;
    }

    // The rest of the method goes here
}

What seems to kick the invoked method through the message pump is when the below code gets executed. This code gets executed after I get tired of waiting for the above delegate to be executed, and I press a button that ultimately calls ClearPrompt().

protected void ClearPrompt()
{
    Debug.WriteLine(DateTime.Now + ": Entering ClearPrompt: " + Thread.CurrentThread.Name);
    this.CurrentPrompt = null;

    if (this.InvokeRequired)
    {
        Debug.WriteLine(DateTime.Now + ": Before BeginInvoke of ClearPrompt: " + Thread.CurrentThread.Name);
        this.BeginInvoke(new Action(() => this.ClearPrompt()));
        Debug.WriteLine(DateTime.Now + ": After BeginInvoke of ClearPrompt: " + Thread.CurrentThread.Name);
        return;
    }

    // The rest of the method goes here
}

And finally, here is the output:

11/13/2013 1:16:49 PM: Entering DisplayPrompt: BatchStateMachine
11/13/2013 1:16:49 PM: Before BeginInvoke of DisplayPrompt: BatchStateMachine
11/13/2013 1:16:49 PM: After BeginInvoke of DisplayPrompt: BatchStateMachine
The thread 0x998 has exited with code 0 (0x0).
The thread 0x1174 has exited with code 0 (0x0).
11/13/2013 1:27:01 PM: Entering ClearPrompt: BatchStateMachine
11/13/2013 1:27:01 PM: Before BeginInvoke of ClearPrompt: BatchStateMachine
11/13/2013 1:27:01 PM: After BeginInvoke of ClearPrompt: BatchStateMachine
11/13/2013 1:27:01 PM: Entering DisplayPrompt: 
11/13/2013 1:27:01 PM: Entering ClearPrompt: 

It's the second Entering DisplayPrompt at 1:27:01 PM that is where the GUI is finally running the delegate.

Question: So I guess the question is, what could cause BeginInvoke() to have this delayed behavior, and what can I do to better debug this?

Other notes:

  1. These methods belong to an instance of a UserControl object
  2. The UserControl object has existed and has been displayed for several minutes before the code has ran, i.e. the control handle should exist.
  3. I have used BeginInvoke() successfully just like this many times before
  4. The GUI seems to be fully responsive during the period in which the delegate should be ran. GUI controls are updating, I can navigate the application, etc.

Thanks!

4

1 回答 1

0

Thanks to Adam Maras's comment, I believe I have tracked down the root cause of the problem. There indeed was a large number of BeginInvoke() calls being performed, but it was not immediately obvious. I have another control that receives a substantial number of updates from a background thread. Not enough to cause a problem normally, but the problem was a growing number of instances of that control that were all receiving updates and performing BeginInvoke() to update the GUI.

The reason for the growing number of controls was that the instances were supposed to be destroyed when navigated away from, and recreated when returning to that view. The controls were however not being destroyed because they had registered for update events from a long-lived class instance and did not unregister from the events when the control was removed from the form. This caused the long-lived class to hold a reference to the UserControl instances in question and all the instances would continue to handle events and update the GUI even though they were no longer even visible.

So even though the GUI was still responsive to some degree, it was unable to fulfill all of the incoming asynchronous calls. I read here that SendMessage messages do not actually get added to the message queue like PostMessage messages do, but rather executed directly by the message pump and have higher priority than the message queue populated by PostMessage. So if normal button clicks and System.Windows.Forms.Timer events are processed via SendMessage events and BeginInvoke() calls are processed on the message queue, I could see how that could happen.

Thanks for all of the helpful and quick suggestions! Who knows how long I may have spent looking for that!

于 2013-11-14T13:36:09.960 回答