Threading

Roey

Junior Contributor
Joined
Oct 10, 2002
Messages
238
Location
Canada
  • UI initiates Client Thread 1 Call to Server (Client Thread 1 Lock)
  • Server Delegates Message to Client (Client Thread 2 starts) *server proessing not yet complete*
  • Client uses Me.Invoke to display (showdialog) form on the UI thread

Problem: The UI thread is locked so the process hangs....

I have tried creating the intital UI call to the server on a different thread but still cannot get it to work.
 
Please elaborate

Your description isn't very clear. You don't mention the relationship between the UI thread, client thread 1 and client thread 2. If a blocking call is made on the UI thread then yes, the UI will be unresponsive. If a blocking call is made on some other thread, it should not affect the UI. You don't mention how client thread 1 ever becomes unlocked, or why the UI thread is blocking. Perhaps you can post the relevant sections of your code?

What it seems you need to do is initiate the call to the server on a worker thread, wait for a response (on the same thread) and then cause some change to the UI by calling Invoke to marshall the call to the UI thread. This sounds like what you are doing, but clearly you are having problems. However, you've only stated that you cannot get it to work so it's difficult to offer any useful advice. If you could elaborate on this that would also be helpful.
 
Thanks for the response. A bit more detail on my problem....

Client - calls a function on the server

decExplossionArray = objRequirements.BOM_Explosion_Costing_Level_One(ItemID)

Server - Does a recursive loop through a list of items until it finds an item that meets a specific criteria (Configurable - requires user input)

The server then uses a delegate function to raise a screen (showdialog) on the client side

Public Delegate Function ConfigurationScreen(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

The user then selects a configurable item from the screen and the result is sent back to the server. The recursive loop then continues searching for configurable items (if any are found the user is once again prompted for input).

Once the recursive loop has finished processing a cost is returned to the orginal screen (UI Thread).
 
Complex threading

You don't mention it, but I assume this whole process is triggered by some user input on the UI. The first thing you should do is use another thread - lets call it thread A - to call BOM_Explosion_Costing_Level_One so that the UI thread is not blocked. As you've described it, the server side code sounds fine.

The delegate method on the client sounds the most problematic. When the server invokes this, it will be on a different thread - lets call it thread B. Normally, you would just test InvokeRequired and if true, marshall the call to the UI thread using Invoke. However, this would cause the original call to return, rather than wait for UI input. Therefore, after calling Invoke, you need to make thread B wait, perhaps using a wait object such as AutoResetEvent. When the necessary input has occurred on the UI and the result is ready to be returned to the server, the UI thread calls Set on the AutoResetEvent, causing the thread B to unblock and be able to return the result to the server.

When the server completes its processing, the BOM_Explosion_Costing_Level_One method returns and thread A can then display the cost - again using Invoke.

Good luck :cool:
 
Thanks once again for you help...

The whole process is triggered by user input in the UI.

The part of your solution that I am stuck on (please bear with me I am totally new to threading) is how to make thread B wait after calling Invoke.

I tried

Dim WaitEvent As Threading.AutoResetEvent
WaitEvent = New Threading.AutoResetEvent(False)

If Me.InvokeRequired Then
Console.WriteLine("Invoke Required")
Dim value_delegate As LoadConfigurationForm_Delegate
value_delegate = AddressOf LoadConfigurationForm
WaitEvent.WaitOne()
Else
Console.WriteLine("Invoke NOT Required")
Dim frmSalesOrderConfigurator As New UISalesOrderConfigurator()
....
WaitEvent.Set()

End If

But the problem is that WaitOne deals with the current thread and doesn't get executed after invoking the delegate.
 
Invoke example

A couple of things.

Firstly, the AutoResetEvent must be declared outside the method, so that the same instance is used on both thread B and the UI thread. You may also need to do this with your return value, depending on what it is. Secondly, nowhere in the code you just posted are you calling Invoke. I will assume that you just forgot to copy that part. This is how I would do it (untested):

Visual Basic:
'At class level
Dim m_configevent As New AutoResetEvent(False)
Dim m_configresult As Integer

'Delegate method
Public Function ConfigScreen(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

    If (Me.InvokeRequired) Then
        'Non-UI thread executing

        'Call this method on the UI thread
        Me.Invoke(AddressOf ConfigScreen, CategoryID, CategoryDescription, ItemID)
        'Wait for UI thread to finish
        m_configevent.WaitOne()

        'Return result
        Return m_configresult
    Else
        'UI thread executing

        'Do stuff relating to user input

        m_configresult = 10 'Result of user input

        'Allow waiting thread to return
        m_configevent.Set()
        Return 0
    End If

End Sub

Good luck :cool:
 
Invoke is synchronous?

I have to say I am a little confused. According to the MSDN documentation, Invoke is a synchronous method. This means it should not return until the call on the UI thread has returned. If that is the case then the following code should work fine:

Visual Basic:
Public Function ConfigScreen(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

    If (Me.InvokeRequired) Then
        'Non-UI thread executing

        'Call this method on the UI thread and return result
        Return DirectCast(Me.Invoke(AddressOf ConfigScreen, CategoryID, CategoryDescription, ItemID), Integer)
    Else
        'UI thread executing

        'Do stuff relating to user input

        Return 10 'Return result to calling thread
    End If

End Sub

Clearly I don't know as much about this issue as I thought. Does this not work?
:confused:
 
I came up with nearly the same method as the first example you posted. However, I found that m_configevent.WaitOne() was only executed after the UI input was complete, which obviously left Thread B still running.

Visual Basic:
'At class level
Dim m_configevent As New AutoResetEvent(False)
Dim m_configresult As Integer

Private Delegate Function ConfigScreen_Delegate(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

Private Function ConfigScreen(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

If (Me.InvokeRequired) Then
        'Non-UI thread executing

        Dim args As Object() = {CategoryID, CategoryDescription, ItemID} ' Make arguments for the delegate.
        Dim cdelegate As ConfigScreen_Delegate    ' Make the delegate.
        cdelegate  = AddressOf ConfigScreen
        Me.Invoke(cdelegate , args) 
        m_configevent.WaitOne()

        'Return result
        Return m_configresult
    Else
        'UI thread executing

        'Do stuff relating to user input

        m_configresult = 10 'Result of user input

        'Allow waiting thread to return
        m_configevent.Set()
        Return 0
    End If

End Function

As regards the last example you posted I got an error on the DirectCast, and didn't understand how this would be stopping Thread B


PS I really appreciate the time you have already spent on this problem....
 
Invoke is synchronous!

Roey said:
However, I found that m_configevent.WaitOne() was only executed after the UI input was complete

This ties in perfectly to what I said in the previous post - Invoke is synchronous. This means it will not return until the invoked delegate has returned, and so eliminates the need for writing synchronization code.

The Invoke method returns an Object which contains the return value of the delegate, if any. Thus it should be possible to cast this to an Integer and then return it, which is what the DirectCast is for.

Adapting your code to this:

Visual Basic:
Private Delegate Function ConfigScreen_Delegate(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

Private Function ConfigScreen(ByVal CategoryID As Integer, ByVal CategoryDescription As String, ByVal ItemID As Integer) As Integer

    Dim retVal As Object

    If (Me.InvokeRequired) Then
        'Non-UI thread executing

        Dim args As Object() = {CategoryID, CategoryDescription, ItemID} ' Make arguments for the delegate.
        Dim cdelegate As ConfigScreen_Delegate    ' Make the delegate.
        cdelegate  = AddressOf ConfigScreen
        retVal = Me.Invoke(cdelegate, args) 

        'Return result
        Return DirectCast(retVal, Integer)
    Else
        'UI thread executing

        'Do stuff relating to user input

        'Return result
        Return 10
    End If

End Function

Good luck :)
 
Back
Top