Roey Posted December 1, 2006 Posted December 1, 2006 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. Quote
MrPaul Posted December 2, 2006 Posted December 2, 2006 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. Quote Never trouble another for what you can do for yourself.
Roey Posted December 2, 2006 Author Posted December 2, 2006 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). Quote
MrPaul Posted December 3, 2006 Posted December 3, 2006 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: Quote Never trouble another for what you can do for yourself.
Roey Posted December 4, 2006 Author Posted December 4, 2006 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. Quote
MrPaul Posted December 4, 2006 Posted December 4, 2006 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): '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: Quote Never trouble another for what you can do for yourself.
MrPaul Posted December 4, 2006 Posted December 4, 2006 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: 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: Quote Never trouble another for what you can do for yourself.
Roey Posted December 5, 2006 Author Posted December 5, 2006 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. '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.... Quote
MrPaul Posted December 9, 2006 Posted December 9, 2006 Invoke is synchronous! However' date=' I found that [b']m_configevent.WaitOne() [/b] 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: 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 :) Quote Never trouble another for what you can do for yourself.
Roey Posted December 9, 2006 Author Posted December 9, 2006 Success.... Thanks so much for your help on this Mr Paul, you have saved the day. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.