Question

I see/use some form or fashion of the code all the time:

public void method1(Object sender, EventArgs args)
{
  if(dataGridView1.InvokeRequired)
    dataGridView1.Invoke(new EventHandler(method1), null);
  else
    // Do something to dataGridView1
}

My question is... What happens to the GUI thread when I use the Invoke? Is it like an interrupt, where the thread will immediately go and execute method1?

Was it helpful?

Solution

Is it like an interrupt...

No, not at all. There is no safe way to interrupt a thread when it is busy executing code. That causes a particularly nasty type of problem called a "re-entrancy bug". It is the kind of bug that firmware programmers battle when they implement interrupt handlers on embedded systems. Some background on that in this web page.

The UI thread of a program solves this a different way, it plays the role of the consumer in the standard solution to the Producer-consumer problem. Ingredients are a thread-safe queue that the producer posts messages to and a dispatcher loop in the consumer thread. Which retrieves messages from the queue and executes the message handler associated with the message. This is quite often described as "pumping the message loop". The producer is most commonly the operating system, generating messages for things like the user pressing a key or moving the mouse. But it can be any code that generates messages, including another thread.

Winforms adds an additional queue to this scheme, the invoke queue. Which stores the delegate object that your code created as well as the arguments you supplied. Begin/Invoke adds an entry to the invoke queue and pinvokes PostMessage() to let the UI thread know that something needs to done.

If the UI thread is busy executing code, say processing a paint event, then it is oblivious to this. It will not notice the posted message until it goes idle again, re-entering the dispatcher loop and calling GetMessage(). Or it already idle, it will then very quickly respond to the message. It retrieves the entry in the invoke queue and execute the delegate target.

In the case of Invoke instead of BeginInvoke, it will then call the Set() method of a ManualResetEvent in the queue entry. Which your thread is waiting for, it will then resume executing. If the delegate method failed then at this point the exception that was raised will also be re-raised in the thread.

Some basic conclusions you can draw from the way it works:

  • How quickly your thread will resume executing entirely depends on how busy the UI thread is
  • In general you want to favor BeginInvoke since that prevents your worker thread from blocking
  • Begin/Invoke calls are automatically serialized, first-in first-out, no additional locking is required
  • However, if you use BeginInvoke then the delegate isn't going to run until later so you have to make sure that any data you supply to the delegate target method is still valid by the time it runs. Which may require locking
  • Using Invoke instead of BeginInvoke can cause deadlock. This will occur when the UI thread is not idle but is waiting for something else to happen. With a guaranteed deadlock when that something else is waiting for your thread to finish. Another good reason to favor BeginInvoke over Invoke
  • You'll have a problem when you invoke faster than the UI thread can execute the delegate targets. The UI thread can never catch up and the invoke queue grows without bound. Easy to notice this, the UI thread stops taking care of low priority duties. Which includes painting, your UI will look frozen
  • You'll have a big problem when your thread is trying to invoke to a form or control that's disposed. Which typically happens when the user closes your window and you don't also ensure that the worker thread stopped running. This usually results in a crash, ObjectDisposedException being the most common outcome

OTHER TIPS

The simple answer is: method1 is called in the current thread (the GUI thread). Its quite similar as:

public void method1(Object sender, EventArgs args)
{
  if(dataGridView1.InvokeRequired)
    method1();
  else
    // Do something to dataGridView1
}

Except that it also executes all the previous methods that has been queued in the marshaller control.

Here some details decompiling Control.Invoke.

As explained on MSDN, Invoke "searches up the control's parent chain until it finds a control or form that has a window handle". Let's call this "parent" control : marshaler.

Then, Invoke calls marshaler.MarshaledInvoke with the delegate to execute as argument.

In MarshaledInvoke, one of the first operation that is performed is to check whether the current thread (the thread that has called Invoke is the same as the thread attached to the window handle of marshaler. It stores the result into a variable syncSameThread.

It enqueues the new task in a queue associated to the marshaler.

Then, if the syncSameThread is true it calls in InvokeMarshaledCallbacks which executes in the current thread all the tasks in the task queue of the current control (here the marshaler).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top