views:

554

answers:

5

From time to time my applications GUI stops redrawing. There a lot of threads that are firing all kinds of events (like timers or network data ready etc.). Also there are a lot of controls that are subscribing these events. Because of that, all the event handlers play the InvokeRequired/Invoke game. Now I figured out that when the GUI freezes a lot of threads are waiting for Invoke() to return. Looks like the message pump stopped pumping. The handlers look like this:

private void MyEventHandler( object sender, EventArgs e ) {
    if ( InvokeRequired ) {
        Invoke( new EventHandler( MyEventHandler ), sender, e );
        return;
    }

    SetSomeStateVariable();
    Invalidate();
}

Any ideas?

Solution: BeginInvoke(). Looks like you should always use BeginInvoke() if you have lots of CrossThread-Events...

Thanks.

Thanks everybody.

EDIT: Looks like BeginInvoke() really solved it. No freezing until now.

+3  A: 

Deadlock perhaps? Do you make sure that the events are never fired while holding a lock?

Are you able to see this with a debugger attached? If so, make it freeze and then hit the "pause" button - and see what the UI thread is doing.

Note that if you are able to get away with BeginInvoke instead of Invoke, life is a bit easier as it won't block.

Also note that you don't need the "new EventHandler" bit - just

Invoke((EventHandler) MyEventHandler, sender, e);

should be fine.

Jon Skeet
Well I did that, but how do I identify the UI thread?
EricSchaefer
Just from memory here: I think you should call the Invoke() on the appropriate control. The control then should execute the method in the gui thread. Please tell me if I am wrong.
lowglider
@lowglider: thats not was I was asking...
EricSchaefer
@EricShaefer: If you open the stack for each thread, it should be pretty obvious which is the UI thread :)
Jon Skeet
*slapsforehead*. Thanks. I try it with BeginInvoke()...
EricSchaefer
+11  A: 

Invoke waits until the event is handled in the GUI thread. If you want it to be asynchronous use BeginInvoke()

Lou Franco
I know that. BeginInvoke would probably just hide the problem. All the Invokes block indefinitely.
EricSchaefer
EricSchaefer, you're missing the point. Invoke waits till the event you called is done. However, the invoke will only be called after the current function is done executing. You're deadlocked. BeginInvoke will not wait for the called function to execute, therefore no deadlock.
Joel Lucsy
Maybe I am missing more than just one point.The event which calls MyEventHandler is fired by another thread (e.g. one that waits for data from the network), not a UI event. Also it freezes only "from time to time".
EricSchaefer
Your problem is that two threads which fired at about the same time are blocking each other- neither Invoke call can complete. BeginInvoke should be safe this way.
Joel Coehoorn
Oh, OK. Now I get it. I will try that. But please don't hold your breath because the problem is not easily reproducible. Thanks.
EricSchaefer
I tried to log the offending Invoke (see Wills post) and the gui did not freeze until now. I changed all Invoke()s to BeginInvoke()s. Thanks a lot.
EricSchaefer
A: 

The most likely answer (deadlock) has already been suggested.

Another way to simulate this behaviour is to reduce the number of pool threads and IO completion ports; you haven't called ThreadPool.SetMaxThreads(...) by any chance?

Marc Gravell
No. (need at least 10 chars for comments...)
EricSchaefer
+2  A: 

From watching this question, I can see that you're not going to get any answers that will fix the problem immediately, as most of them require you to debug the event, and it happens so infrequently that this is nearly impossible. So, let me suggest you make some code changes that might help you identify the culprit in the field.

I suggest that you create a static class whose sole purpose is to handle all your Invoke calls. I would suggest that this class has a method that takes a Control, (to call Invoke on) an Action (the method to be invoked), and a description (containing the information you would need to know to identify the method and what it is going to do).

Within the body of this method, I suggest you enqueue this information (method, description) and return immediately.

The queue should be serviced by a single thread, which pops the action/message pair off the queue, records the current time and the Action's description in a pair of properties, and then Invokes() the Action. When the Action returns, the description and time are cleared (your DateTime can be nullable, or set it to DateTime.Max). Note, since all Invokes are marshalled one at a time onto the UI thread, you're not losing anything by servicing the queue by a single thread here.

Now, here's where we get to the point of this. Our Invoking class should have a heartbeat System.Threading.Timer thread. This should NOT be a windows.forms.timer object, as that runs on the UI thread (and would be blocked when the ui is blocked!!!).

The job of this timer is to periodically peek at the time the current Action was Invoked. If DateTime.Now - BeginTime > X, the heartbeat timer will decide that this Action has blocked. The heartbeat timer will LOG (however you log) the DESCRIPTION recorded for that Action. You now have a recording of what was happening at the time your UI locked up and can debug it better.

I know it's not an answer to your problem, but at least by doing this you can get a good idea about what's going on at the time you're blocked.

Will
Good ideas. I will have to touch all 55 (!) Invokes anyways.
EricSchaefer
The freezing did not happen, when I did what you suggested. So I changed all Invoke()s to BeginInvoke()s.
EricSchaefer
A: 

You can avoid the use of Invoke using the object SynchronizationContext that was introduced in framework 2. Ok , it's the same thing, you substitute one thing for another, but in fact, it is more efficient and robust.

Read about this in: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx http://codingly.com/2008/08/04/invokerequired-invoke-synchronizationcontext/

Some code to expose my idea:

private Thread workerThread;

        private AsyncOperation operation;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            operation = AsyncOperationManager.CreateOperation(null);

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }

        private void DoWork()
        {
            operation.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if(handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }), null);

            operation.OperationCompleted();
        }
netadictos