Does a call to Control.Invoke() block until invocation is finished?

cjard

Well-known member
Joined
Apr 25, 2006
Messages
7,081
Programming Experience
10+
Hi All

Quick question as I'm implementing a thread safe way of reading the value from a control

In an app, I have the need to have a background worker enumerate an array of controls and read their values. To solve it I did this (

VB.NET:
 Class SingleChoiceArgument 
   Inherits Arguments.GenericArgument 
 Private cb As ComboBox 
 Private toRet As Object = "" 
 Private waitingThread As System.Threading.Thread 
 
 Private Delegate Sub TransferValueCallback() 
 
 Public Sub New(ByVal dispValMembers As RealDS.DispValDataTable) 
   [COLOR=seagreen]'snip[/COLOR]
 End Sub 
 
 Public Overloads Overrides Function GetValue() As Object 
   [COLOR=seagreen][I]'if current thread did not create the combobox[/I][/COLOR]
   If cb.InvokeRequired Then 
     [COLOR=seagreen][I]'new delegate[/I][/COLOR]
     Dim tvcb As TransferValueCallback = AddressOf transferValueSafely 
     [COLOR=seagreen][I]'store the current thread reference so we can wake it up later[/I][/COLOR]
[COLOR=seagreen][I]    [COLOR=black]waitingThread = System.Threading.Thread.CurrentThread[/COLOR] [/I][/COLOR]
[COLOR=seagreen][I]    'bein an invoke, which will make the combo's thread copy the value for us[/I][/COLOR]
[COLOR=seagreen][I]    'it will then attempt to join the waiting thread.. i.e. it will wait for the waiting[/I][/COLOR]
[COLOR=seagreen][I]    'thread, or it will wake it from its sleep[/I][/COLOR]
     cb.Invoke(tvcb) 
     [COLOR=seagreen][I]'put the current thread to sleep[/I][/COLOR]
     System.Threading.Thread.Sleep(10000) 
   Else 
     toRet = cb.SelectedValue 
   End If 
   Return toRet 
 End Function 
 
 Private Sub transferValueSafely() 
  [COLOR=seagreen][I] 'the GUI thread will move the value for us..[/I][/COLOR] 
   toRet = cb.SelectedValue 
   [COLOR=seagreen][I]'it will then wake up the sleeping worker thread[/I][/COLOR]
   waitingThread.Join 
 End Sub 
End Class

I find that the app hangs, and a look at the thread window seems to indicate that one thread is stopped at Invoke, and the other is stopped on the Join..

As you can probably guess, I was coding to avoid the situation whereby the worker thread goes and grabs the value before the gui thread has moved it..

Given that the app blocks, i think the UI thread is waiting for the worker thread, which is blocked on the call to Invoke..

Hence my question: Does Invoke() block until the GUI thread has finished? If it does, then I can ignore the need to make the worker thread sleep.. If it does not, then a) why does the app hang, and b) how to ensure that the worker thread doesnt attempt to retrieve the value before the gui thread has finished copying it?
 
It seems that Join, should be changed for Interrupt(), and the ThreadInterruptException that the sleeping thread experiences, should subsequently be handled... Join waits for the thread to terminate, which isnt quite what we want.. DOh
 
Invoke blocks, so you should remove the sleep. You must also remove Join from the UI invoke (it is the UI thread that runs this method!), it will make the UI thread wait for other thread to finish, which it doesn't since it is waiting for this same Invoke block call to finish. This finishes cleanly without exception. No need to Interrupt when that thread was not in sleep/wait/join state, which it wasn't when you removed the unnecessary sleep.
 
Thanks John..


I'll make those changes. The code as it stands is:
VB.NET:
Class SingleChoiceArgument 
   Inherits Arguments.GenericArgument 
 Private cb As ComboBox 
 Private toRet As Object = "" 
 
 Private Delegate Sub TransferValueCallback() 
 
 Public Sub New(ByVal dispValMembers As RealDS.DispValDataTable) 
   'snip
 End Sub 
 
 Public Overloads Overrides Function GetValue() As Object 
   'if current thread did not create the combobox
   If cb.InvokeRequired Then 
     'new delegate
     Dim tvcb As TransferValueCallback = AddressOf transferValueSafely 
     'store the current thread reference so we can wake it up later
    waitingThread = System.Threading.Thread.CurrentThread 
     'get the UI thread to transfer the value for us
     cb.Invoke(tvcb) 
     'put the current thread to sleep
     Try
       System.Threading.Thread.Sleep(10000) 
     Catch tie as ThreadInterruptException
       'nothing needed to do here
     End Try
   Else 
     toRet = cb.SelectedValue 
   End If 
   Return toRet 
 End Function 
 
 Private Sub transferValueSafely() 
   'the GUI thread will move the value for us.. 
   toRet = cb.SelectedValue 
   'it will then wake up the sleeping worker thread
   waitingThread.Interrupt()
 End Sub 
End Class


While wondering whether Invoke blocked or not, I put a breakpoint on cb.Invoke() and also on transferValueSafely()

Single stepping it, I was surprised to see that the worker thread didnt pause at Invoke (because the thread it was waiting for was suposed to be paused by the breakpoint on transferValueSafely

Is it the case that we cannot work out if a method blocks, like this? Why did the debugger not switch to the thread that was calling transferValueSafely()? Did that thread ignore the breakpoint, or does the debugger not work in this way? (I wondered if it would switch to any thread not-blocked, and run it till it hit a breakpoint..)


Ends up, if youre certain that Invoke blocks, then I can remove the Sleep()/Interrupt() ; at present it seems to work because if Interrupt is called on a sleeping thread, it wakes up.. and the other way round, if Interrupt is called on a running thread, the next time it tries to go to sleep, it will interrupt immediately.. If Invoke (or any similar scenario) didnt block and the worker thread had to wait for the UI thread to finish, I guess this would guarantee it?
 
If you remove the Interrupt from transferValueSafely method and remove the Sleep from GetValue method it will run smoothly without hanging or exception.

Continuing the curiousity: When I tested this with a fast machine it was the subsequent Sleep that threw the interrupt exception, not the invoke call. Now I try with a slow machine and it is the Invoke call that throws the interrupt exception (which lead me to believe that Interrupt call put a 'mark' on the thread and not necessarily stops it immediately). You can read the thread state of the Invoke call from transferValueSafely method with this code:
VB.NET:
MsgBox(waitingThread.ThreadState.ToString)
The answer is WaitSleepJoin state, and the exception message says it was interrupted from Wait state from Invoke call.
 
Continuing the curiousity: When I tested this with a fast machine it was the subsequent Sleep that threw the interrupt exception, not the invoke call. Now I try with a slow machine and it is the Invoke call that throws the interrupt exception.

Ah, so my statement of "Interrupting a running thread will cause the thread to experience an interrupt next time it goes to sleep" is not accurate.. If the UI thread interrupts the worker thread before it has reached the sleep state, then it jumps straight to the exception handler (as it would if it was half way through X lines of code):

Pseudocode:
VB.NET:
workerThread = Thread.CurrentThread
cb.Invoke(transferValueCallBack)
codeline1
codeline2
codeline3
codeline4
codeline5
Try
  Thread.Sleep(1000)
Catch InterruptedException
End Try
 
Sub TransferValueSafely()
  codelineA
  workerThread.Interrupt()
End Sub

So if my code were thus, and Invoke did not block, and the threads processed at exactly the same speed and events occurred simultaneously (hypotehtical machine) then
workerThread would Invoke, causing the UI thread to start running
workerThread runs codeline1, while UIthread runs codelineA
workerthread runs codeline2, while UIthread interrupts workerThread
app dies because workerthread raised an InterruptedException that was outside of a handler


The fact that the fast machine's workerThread made it to the Sleep() call before it was interrupted; does that not infer that Invoke doesnt block?


Heh.. one block I'm sure of is the mental one in my head...
If Invoke blocks, surely the workerThread should never reach Sleep(), and an Interrupt of workerThread would kill the thread (Unhandled exception)?
 
I thought a bit more about what you wrote in brackets, and it seems a sensible thought! If you ever find out truely why, I'm sure youll let me know..


I had a thought that i could simplistically check whether invoke blocked; I put a loop after invoke that prints the number 2 to the console ten thousand times. In the transferValueSafely i put a similar loop printing the number 1 ten thousand times. If both threads ran simultaneously then 1121212121212121212 would be seen, but as it was your original advice was perfectly accurate, and
11111111111111111111111111111...222222222222222222222222222 was seen
 
I think it is because Interrupt method is not blocking itself, it just marks the thread ready for interuption, so the fast machine would be able to finish Invoke call and make it to the next statement, which the slow machine didn't make in time. The Control.Invoke method is indeed blocking.

Here is my suggestion for your thread-safe GetValue function:
VB.NET:
        Private Delegate Function GetValueSafe() As Object
        Public Overloads Overrides Function GetValue() As Object
            If cb.InvokeRequired Then
                Return cb.Invoke(New GetValueSafe(AddressOf GetValue))
            Else
                Return cb.SelectedValue
            End If
        End Function
 
If I could give you rep for this, I would! (I must spread some around, apparently)

Thanks muchly, John!

Incidentally, here's what I did, in the end, to ensure all my inheriting children objects (with their embedded controls) return their values in a threadsafe manner:

VB.NET:
Imports System 
Imports System.Collections.Generic 
Imports System.Text 
Imports System.Windows.Forms 
 
Namespace Miramar.Arguments 
 
 Public MustInherit Class GenericArgument 
   Protected ctrl As Control 
 
   Private Delegate Function GetValueDeleg() As Object 
 
   Public Function GetValue() As Object 
     If ctrl.InvokeRequired Then 
       Return ctrl.Invoke(AddressOf GetValueThreadSafeImpl) 
     Else 
       Return GetValueThreadSafeImpl 
     End If 
   End Function 
 
   Protected MustOverride Function GetValueThreadSafeImpl() As Object 
 
 End Class 
End Namespace

Now, no matter what, the UI thread is ensured to be the one to perform GetValueThreadSafeImpl.. I hope! :)
 
I think that should work, except you forgot to wrap in the delegate:
VB.NET:
Return ctrl.Invoke[COLOR=darkgreen](New GetValueDeleg[/COLOR](AddressOf GetValueThreadSafeImpl))
 
Interestingly, that wasnt me! This is in C#:
VB.NET:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Miramar.Arguments
{
  abstract public class GenericArgument
  {
    protected Control ctrl;

    private delegate object GetValueDeleg();


    public Control EditorControl
    {
      get { return ctrl; }
    }

    private String argumentName;
    public String ArgumentName
    {
      get { return argumentName; }
      set { argumentName = value; }
    }
    private System.Data.OracleClient.OracleType argumentType;
    public System.Data.OracleClient.OracleType ArgumentType
    {
      get { return argumentType; }
      set { argumentType = value; }
    }

    public object GetValue()
    {
      //if this method is being accessed by a thread other than the one that
      //created cb, then call cb.Invoke which will make the UI thread that created
      //the control, come in and return the value back to the blocked worker thread
      //that called invoke. The worker then returns the value it was passed by the UI
      //thread
[B]      if(ctrl.InvokeRequired)
        return ctrl.Invoke(new GetValueDeleg(GetValueThreadSafeImpl));
      else
        return GetValueThreadSafeImpl();[/B]
    }

    protected abstract object GetValueThreadSafeImpl();

    public System.Data.OracleClient.OracleParameter ToOracleParameter()
    {
      System.Data.OracleClient.OracleParameter op = new System.Data.OracleClient.OracleParameter();
      op.ParameterName = argumentName;
      op.OracleType = argumentType;
      //op.SourceColumn = argumentName;
      op.Value = GetValue();
      return op;
    }
  }
}

And I used DeveloperFusion's c#<->VB converter to turn it into VB syntax.. Im dimly aware it does this via partial comp/decomp of c#->AST->VBN

and straight up, that's what the decompilation gave (missing deleg wrapper).. Oops..

Does it compile? :)
 
DeveloperFusion's c#<->VB converter isn't very reliable! It's nice in some cases to get much code quick translated somewhat, but in most cases adjustments have to be made to get working codes. Also I think it is only .Net 1.1 compatible. And there is always certain stuff it just doesn't handle at all. You should absolutely not take C# code, convert it there, then post it unverified at forums making the assumption you are posting working code. I have used this tool a lot, and find it very useful, but have to rewrite the translation often.
 
Back
Top