Tuesday, February 27, 2007

This is another post in a series that deals with asynchronous behaviour (see also here and here).

The difference between Control.Invoke() and Control.BeginInvoke() is:

BeginInvoke() will schedule the asynchronous action on the GUI thread. When the asynchronous action is scheduled, your code continues. Some time later (you don't know exactly when) your asynchronous action will be executed.

A logical conclusion is that a delegate you pass to Invoke() can have out-parameters or a return-value, while a delegate you pass to BeginInvoke cannot (you have to use EndInvoke to retrieve the results).

My advice is: always use BeginInvoke() if you can. Reasons:

Because Invoke() causes one thread to wait for another thread, it is likely to cause deadlocks. Imagine that your application has a dedicated worker thread that uses Invoke() to execute some work on the GUI thread. Now image that during this work, your GUI code needs to execute something on the worker-thread. It will be blocked forever because your worker-thread is already busy waiting for your GUI-thread. In this simple example it might be easy to spot the problem. In a big application developed by many people it will not always be this easy. Therefore it is better to find a pattern that avoids blocking altogether.

Note that in this example you might think that a threadpool solves your problem, because then you have multiple workerthreads. However the problem will still occur under high load, this is called threadpool starvation and it is a very complex problem. Because it only occurs under high load, it is a very dangerous thing that is very difficult to debug.

Invoke() will cause forced threadswitches that will decrease the scalability of your application.

First consider BeginInvoke: when it is called, a delegate is scheduled to be executed on the other thread. However your current thread will continue until the operating system decides that it's done enough work. Only then will the other thread (GUI thread) be executed, and your delegate will be run. If other delegates were also scheduled using BeginInvoke(), they will be executed as well.

Now consider Invoke: when you call it, the OS immedeately has to stop your current thread and schedule the other thread. When more than one Invoke()-statement is done, a threadswitch will be needed every single time (whereas multiple BeginInvoke-requests can be bundled in one threadswitch). Excessive threadswitches will seriously hurt performance when your application is under a high load.

Conclusion: avoid using Invoke().

20 comments:

Patrick Kursawe
said...

Thank you very much - I immediately ran into a deadlock when trying to use Invoke(), nice to see that there's an easy way out.

I ran into the same issues when upgrading vs2003 to vs2005. Fortunelty I had a collegue who had experience in delgate use to showed me what was needed to do. Even after seeing these example

It was just so simple after that. Haven't incountered any issues yet with the Invoke() command over the program however given each thread only updates a particular cell in the listveiw hence no thread is really fighting for the same real estate, may have something to do with it. Anyway here is my solution seems very easy now.

clsthread.cs

// All the code is in the thread.class no need to putanything in the main form

Just to make it clear.... if you are having problems with threads on a control - don't use CheckForIllegalCrossThreadCalls as I have been doing but call the form method with BeginInvoke. I think you cannot pass parameters so set them up in form variables. Example below....

Just to make it clear - don't useCheckForIllegalCrossThreadCalls as I have been doing but call your method on the form with begininvoke. I think you cannot pass parameters so set them in the form variables. Example below.

@ear: the InvokeRequired-pattern is mostly used for simple GUI updates. It should not matter if this is synchronous or asynchronous. But if it does matter, then you might choose a mechanism that is more consist (e.g. always using BeginInvoke even if it is not entirely necessary).

@ear: if you really want synchronous behaviour, then you'll have to use Invoke(). You could use BeginInvoke() in combination with a ManualResetEvent, but that is probably what Invoke() is doing behind the scenes as well.But generally speaking, Invoke/BeginInvoke are mostly used to update the GUI. And why would you need to do this synchronously?

One reason why asynchronous might not be appropriate for updating UI is if the user clicks a button in a quick, repetitive fashion. In this case if you use BeginInvoke and the code you're invoking (my case was loading a document into a 3rd party view control) this might take too long and cause the requests to pile up. When the user finally paused their clicking so that the application could catch up the result was that the remaining scheduled beginInvokes executed in 'LastInLastOut' fashion. So they actually were looking a document that was from several clicks ago.

Though, this 3rd party component was COM so perhaps this behavior was due to the extra layer.