Tip Invoke example for dummy's - Change one threads label from another thread

Gopher2011

Well-known member
Joined
Mar 3, 2011
Messages
111
Location
South England
Programming Experience
10+
Ok, after a long time trying to get one thread to talk to another thread, the whole thing was driving me nuts but its quite simple so I decided to put the code here to help people like me.

I know there are longer better more complex more complete examples there but I just wanted to change a label on another thread, not get men to the moon so please do forgive me if this is simple to some of you. The stop button does not do anything, but if the main thread was in a loop the code would have an avenue to escape the loop. I will cover this next.

Ok lets go

VB.net 2010 installed. This apparently only works in 2010 version [so I am told]. Upgrade ok.

New windows form project. Call the new form Main.vb Add 5 new objects listed below to the form.

Add a label - name is 'Label1'
Add a label - name is 'Label2'

Add a button - name is 'btn_Start'
Add a button - name is 'btn_Stop'

Add a timer - name is Timer1

Now copy this code to the new form you have created.

VB.NET:
Imports System.ComponentModel

Public Class Main
    Private WithEvents NewThread As BackgroundWorker

    Public Sub New()
        InitializeComponent()                   'default added by windows
        NewThread = New BackgroundWorker        'Initiate new thread

        Debug.WriteLine("NewThread will let other methods be aware of it")
        NewThread.WorkerReportsProgress = True

        Debug.WriteLine("NewThread will allow cancelation")
        NewThread.WorkerSupportsCancellation = True

        'This shows the main form thread constantly adding a '.' to Label1
        Timer1.Enabled = True
    End Sub

    Private Sub btn_Start_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) _
                                Handles btn_Start.Click

        Debug.WriteLine("btn_Start_Click")
        NewThread.RunWorkerAsync()                        'Run background thread
    End Sub

    Private Sub btn_Stop_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) _
                                Handles btn_Stop.Click

        Debug.WriteLine("btn_Stop_Click")
        NewThread.CancelAsync()                             'Stop thread
    End Sub

    Private Sub NewThread_DoWork(ByVal sender As System.Object, _
                                ByVal e As System.ComponentModel.DoWorkEventArgs) _
                                Handles NewThread.DoWork

        Debug.WriteLine("NewThread_DoWork")

        'Makes a change on the main form thread from thread NewThread
        If Label2.InvokeRequired Then Label2.Invoke(Sub() Label2.Text = "New Label 2 Text")
        'Makes a change on the main form thread from thread NewThread
        If Label2.InvokeRequired Then Label2.Invoke(Sub() Label2.Visible = False)

        'Demonstrates thread NewThread sleep is independant of teh main thread
        System.Threading.Thread.Sleep(2000)
        MessageBox.Show("I was sleeping for 2 seconds")

        'Makes a change on the main form thread from thread NewThread
        If Label2.InvokeRequired Then Label2.Invoke(Sub() Label2.Visible = True)

    End Sub

    Private Sub NewThread_ProgressChanged(ByVal sender As Object, _
                                ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
                                Handles NewThread.ProgressChanged

        Debug.WriteLine("NewThread_ProgressChanged")
    End Sub

    Private Sub NewThread_RunWorkerCompleted(ByVal sender As System.Object, _
                                ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
                                Handles NewThread.RunWorkerCompleted

        Debug.WriteLine("NewThread_RunWorkerCompleted")
    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Label1.Text = Label1.Text & "."
        If Label1.Text.Length > 40 Then Label1.Text = "."
    End Sub

End Class



Now you can click run then when the form opens click start. You will get a '......' line increasing by another '.' every second. The Label2 goes invisible (to fast to see it), the thread pauses 2 seconds then the Label2 becomes visible with new text.

That is how you change form objects from another thread, nice and simple for
dummy's...


To prove my point if you add into the module

VB.NET:
 Private Sub NewThread_DoWork(ByVal sender As System.Object, _
                                ByVal e As System.ComponentModel.DoWorkEventArgs) _
                                Handles NewThread.DoWork

 'other text missed out for clarity

    Label2.Text = "New label"

 End Sub

You will get an error after hitting the 'start' button when it tries to execute the code you will get a

System.InvalidOperationException Error.
{"Cross-thread operation not valid: Control 'Label2' accessed from a thread other than the thread it was created on."}

Have fun with it!
 
Last edited:
All you had to do was ask :)

Look at the following code :-

VB.NET:
    Delegate Sub SetLabel1TextInvoker(ByVal TextToDisplay As String)

    Public Sub SetLabel1Text(ByVal TextToDisplay As String)
        If Label1.InvokeRequired Then
            Label1.Invoke(New SetLabel1TextInvoker(AddressOf SetLabel1Text), New Object() {TextToDisplay})
        Else
            Label1.Text = TextToDisplay
        End If
    End Sub

and to use it, it's as simple as :-

VB.NET:
    Private Sub NewThread_DoWork(ByVal sender As System.Object, _
                                ByVal e As System.ComponentModel.DoWorkEventArgs) _
                                Handles NewThread.DoWork

        Debug.WriteLine("NewThread_DoWork")

        'Makes a change on the main form thread from thread NewThread
        SetLabel1Text ("Hello")

    End Sub

See :)
 
Hey thanks - I still don't really follow the delegate bit -

VB.NET:
    [U][B]Delegate[/B][/U] Sub SetLabel1TextInvoker(ByVal TextToDisplay As String)

    Public Sub SetLabel1Text(ByVal TextToDisplay As String)
        If Label1.InvokeRequired Then
            Label1.Invoke(New SetLabel1TextInvoker(AddressOf SetLabel1Text), New Object() {TextToDisplay})
        Else
            Label1.Text = TextToDisplay
        End If
    End Sub

vs my code

VB.NET:
     If Label2.InvokeRequired Then Label2.Invoke(Sub() Label2.Visible = True)

Which i think may be easier for slow folks like me. I am just so not getting the delegate part..
I do apologise big time ok, but i just cant get it to work [the code works, but not my head in following the need to declare it].. Maybe I should go back to COBOL...:s
 
Last edited:
Hey thanks - I still don't really follow the delegate bit -
VB.NET:
     If Label2.InvokeRequired Then Label2.Invoke(Sub() Label2.Visible = True)
What you're doing here is to use a lambda expression, Lambda Expressions (Visual Basic)
A lambda expression is a function or subroutine without a name that can be used wherever a delegate is valid.
So in effect it is a shortcut to defining a delegate and a named method, creating an instance of the delegate pointing it to the method, and passing the delegate instance to the Invoke method.
Delegates in Visual Basic
A delegate is a form of object-oriented function pointer that allows a function to be invoked indirectly by way of a reference to the function.

Btw, using a BackgroundWorking you never need to invoke, because you can pass any information from UI thread to the worker by RunWorkerAsync method, and any information from worker to UI thread by the progress/completed events.
 
Ok Thanks JonH. Thats good info to know - and I am slowly digesting it - its appreciated.

Going back InertiaM's comments:

I made this partially - work - I really appreciate the help you provided two weeks ago - it all works - I am now at the 'trying to understand what you did phase'.

This following code based on the example provided by InertiaM above.
It works fine, and I have learnt a lot.


VB.NET:
    Delegate Sub DisplayInfoMessageInvoker(ByVal TextToDisplay As String)
    Public Sub DisplayInfoMessage(ByVal TextToDisplay As String)
        If lbl_Info.InvokeRequired Then
            lbl_Info.Invoke(New DisplayInfoMessageInvoker(AddressOf DisplayInfoMessage), New Object() {TextToDisplay})
        Else
            lbl_Info.Text = TextToDisplay
            tmr_InfoText.Enabled = True
            MessageDisplayTimer = 10
        End If
    End Sub

However the above code was modified to go from changing a label text to sending data out the com port.

It reaches the invoke part [Debug.WriteLine("InvokeRequired")] (the debug tells me this), but actually does not squirt out the com port. When I return to the main thread and do a comport action - it squirts out the accumulated comport out actions.

So by the following code if I send a,b,c from a thread, - when I return to my main form and press the send button and send an 'a' from my 'main form' the buffer I get 'abca'

So what I am trying to do is from a thread - write to the comport. The reason I want to do this is to put timers in that do not affect the main thread.


VB.NET:
    Delegate Sub ComPortWriteInvoker(ByVal TextToSend As String)
    Public Sub SendComWrite(ByVal TextToSend As String)
        If InvokeRequired Then
            Debug.WriteLine("InvokeRequired")
            MyComPort.Write(TextToSend)
            'lbl_Info.Invoke(New DisplayInfoMessageInvoker(AddressOf SendComWrite), New Object() {TextToSend})
        Else
            Debug.WriteLine("No InvokeRequired")
            MyComPort.Write(TextToSend)
        End If
    End Sub
 
Dont try and do COM port communication on multiple threads. The best solution I have found is to add the information (data) to be sent to a Queue (of T), and then use a separate thread to extract (dequeue) from there, and send the data to the port.

The other reason for using a Queue is that everything will get sent in the order you put it into the Queue :)
 
Back
Top