Question cross thread call problem

dxmedia

Member
Joined
Jul 2, 2010
Messages
16
Programming Experience
1-3
Hi,

Even though there are many examples of Invoke operations, I can't find something that works for me. (I'm using VS 2008, and working in VB)

Here is what i have:

A main module with functions (thread 1) that starts a BeginRead for a TCPClient. When the callback function executes (on thread 3), I want to call a function from the main module, in the main thread. All I get even with Invokes is calling functions that always run in thread 3, not the parent thread.

I call:

MsgBox("Function name: " & Thread.CurrentThread.ManagedThreadId)

in each of my functions do determine on what thread it is executed. At some point, from the infinite read callback function I will have to get a function to show a form, but that function gets caught in the callback thread and freezes, instead of running in the main thread.

Any suggestions ?

Thanks

PS: the callback that I pass to the BeginRead of the TCPClient is a function, not a class, so I can't use raise events.

All I need is to call a function to run in the main thread (main module) from another thread.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+

dxmedia

Member
Joined
Jul 2, 2010
Messages
16
Programming Experience
1-3
Hi,

Thank you for your reply.

I know that, it's pretty easy with forms and controls, but I do not have a form yet, only a module.

The application first tries to connect to the server, then the form is created. I want to do this login with code only, not through "hidden" forms.

I need to call a function on the main module thread. This is where I am having trouble.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+
I think you need to explain further. Perhaps you have made async calls when you should have used synchronous, and now have trouble getting back on line? Or perhaps you should be calling this ascyn from UI? Without a UI thread, and message loop, the only concern of multithreading is synchronization, what thread a call is done from is not relevant, but the order and access of things is arranged.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,746
Location
Sydney, Australia
Programming Experience
10+
I don't really see how what you're trying to do makes sense. If you have no form yet then why do you need to read asynchronously? You would normally use asynchronous methods so that your current thread is free to do other things in the mean time, e.g. the UI thread can update the UI. What is your main thread doing while this asynchronous read is taking place?
 

dxmedia

Member
Joined
Jul 2, 2010
Messages
16
Programming Experience
1-3
Hi,

1. the application starts with a main module, that starts a thread loop with: Application.Run()

2. Yes, I know I can make it synchronus, but the application has other things to do in the meantime, plus this connection will remain open for the entire length of the application, and I do not need a synchronus function to freeze my application.

3. I want the controlling code of the application to be in a module, because I dot need to be stuck to a form I can never close. This way I can open and close any application form without closing the application itself.

4. Still looking to call a sub on main thread from a sub on another thread (without using forms, suppose it was a console application ?)
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+
Application.Run is what starts the message loop. This call does not return until that message loop is closed. As per documentation you should supply the form parameter, so that the loop closes when form is close, this also means you do have the form instance you can use to Invoke from. Where the method that runs your background work is defined is not relevant, but to call Invoke you need that form reference available.

This example has application framework disables and runs off a Sub Main. It starts a background task and then runs a form, background task sends a message to form (if not already closed), when message loop (form) closes if uses ManualResetEvent to ensure background task completes before ending app. Try run it and wait for UI to update, run it again and this time close close UI before it updates.
VB.NET:
Module Module1

    Private UI As Form, wait As New Threading.ManualResetEvent(False)

    Sub main()
        Debug.WriteLine("T1: app started, starting background task.")
        Threading.ThreadPool.QueueUserWorkItem(AddressOf worker)
        UI = New Form1
        Debug.WriteLine("T1: starting message loop and displaying form.")
        Application.Run(UI)
        Debug.WriteLine("T1: form is closed, app is still running.")
        Debug.WriteLine("T1: wait for worker to complete.")
        wait.WaitOne()
        Debug.WriteLine("T1: worker done, ending now.")
    End Sub

    Sub worker()
        Debug.WriteLine("T2: background task started, runs for 3 seconds.")
        Threading.Thread.Sleep(3000)
        wait.Set()
        If Not UI.IsDisposed Then
            Debug.WriteLine("T2: sending message to UI.")
            UI.Invoke(New MethodInvoker(AddressOf worker_complete), Nothing)
        Else
            Debug.WriteLine("T2: UI was already closed.")
        End If
    End Sub

    Sub worker_complete()
        UI.Text = "T1: background work is done."
    End Sub
End Module
 

dxmedia

Member
Joined
Jul 2, 2010
Messages
16
Programming Experience
1-3
Thank you for your reply, and yes, I know your solution works and I did this many times before. Now what I need is to call a sub on the main thread from a sub running on another thread WITHOUT forms. I know it's not easy, but this is what I need.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+
That doesn't make sense. A thread can only carry out a set of instructions, when it is doing this it is busy, when it is done it is finished. You can't force instructions from a different thread to be inserted in another one. The point of the UI message loop is that it checks for queued tasks to be carried out, and executes them one by one. These tasks are 'messages' and the base for the event model. As I said, without the message loop you only need to care about the access of object from different threads, and the order in which things happen between different threads, this is something you manage with the threading tools available.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,746
Location
Sydney, Australia
Programming Experience
10+
Thank you for your reply, and yes, I know your solution works and I did this many times before. Now what I need is to call a sub on the main thread from a sub running on another thread WITHOUT forms. I know it's not easy, but this is what I need.

So you say but I very much doubt that that is the case. Regardless, you can use the SynchronizationContext to marshal a method call to a specific thread under any circumstances. You get an instance on a specific thread and then you can use that instance to marshal a call to that thread from any other thread. Here's a code example:
VB.NET:
Private context As Threading.SynchronizationContext = Threading.SynchronizationContext.Current

Private Sub Form1_Load(ByVal sender As System.Object, _
                       ByVal e As System.EventArgs) Handles MyBase.Load
    Threading.Thread.CurrentThread.Name = "UI Thread"
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) Handles Button1.Click
    Me.context.Send(AddressOf Me.ResetTextBoxText, Nothing)

    Me.BackgroundWorker1.RunWorkerAsync()
End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
                                     ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Threading.Thread.CurrentThread.Name = "Worker Thread"

    Me.context.Send(AddressOf Me.ResetTextBoxText, Nothing)
End Sub

Private Sub ResetTextBoxText(ByVal userState As Object)
    Debug.WriteLine(Threading.Thread.CurrentThread.Name, "ResetTextBoxText")

    Me.TextBox1.ResetText()
End Sub
That example is from a form but the form plays no part in marshaling the method call across the thread boundary.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+
Regardless, you can use the SynchronizationContext to marshal a method call to a specific thread under any circumstances.

That example is from a form but the form plays no part in marshaling the method call across the thread boundary.
Actually it does, the specific context instance that 'makes this happen' is WindowsFormsSynchronizationContext, and guess what, it does this by calling Control.Invoke! You only get a context object for a thread that has created a control. We discussed this before, and while creating a control is simple enough the 'receiving' thread also need to make Application.DoEvents calls to explicitly let the waiting calls in. A shortcut here is to create the WindowsFormsSynchronizationContext instance directly, which causes a 'dummy' marshalling control to be created, but DoEvents is still necessary. It is no different to having a queue of tasks, and have the appropriate thread process that queue once in a while.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,746
Location
Sydney, Australia
Programming Experience
10+
Actually it does, the specific context instance that 'makes this happen' is WindowsFormsSynchronizationContext, and guess what, it does this by calling Control.Invoke! You only get a context object for a thread that has created a control. We discussed this before, and while creating a control is simple enough the 'receiving' thread also need to make Application.DoEvents calls to explicitly let the waiting calls in. A shortcut here is to create the WindowsFormsSynchronizationContext instance directly, which causes a 'dummy' marshalling control to be created, but DoEvents is still necessary. It is no different to having a queue of tasks, and have the appropriate thread process that queue once in a while.

Hmmm... that I was not aware of. I might have to do a bit more research on the topic, but information on the SynchronizationContext is a bit thin on the ground. I've searched before and I'll search again but, if you have any links to good sources of information JohnH, feel free to post them here. :thumb:
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,609
Location
Norway
Programming Experience
10+
Hmmm... that I was not aware of. I might have to do a bit more research on the topic, but information on the SynchronizationContext is a bit thin on the ground. I've searched before and I'll search again but, if you have any links to good sources of information JohnH, feel free to post them here. :thumb:
This is where we discussed control.invoking to any thread: http://www.vbdotnetforums.com/vb-ne...35879-executing-delegate-existing-thread.html
WindowsFormsSynchronizationContext I noticed when debugging SynchronizationContext.Current, and the other information I found when reading the source code through .Net Reflector. Its constructor gets ThreadContext.MarshalingControl, a property that creates the control if not set.

This article also discusses the internals: Understanding SynchronizationContext (Part I) - CodeProject
And this follow up seems to set up a custom message queue: Understanding SynchronizationContext: Part II - CodeProject
 
Top Bottom