Invoke UI Changes Across Threads on VB .Net

I need to do this all the time and don’t have the best memory in the world. Today, I decided that I looked this up one too many times so here’s my solution to this multithreading problem:

The Problem:

You try to modify UI components created in one thread in another thread. In VB, you can only make UI changes on the same thread that created it so you get a nice “Cross thread operation not valid” exception.

Here’s the wrong code:

thread = New System.Threading.Thread(AddressOf DoStuff)
thread.Start()
Private Sub DoStuff()
    'error occurs here'
    Me.Text = "Stuff"
End Sub

The Solution:

If you’re super lazy or you’re very sure of what your code is going to do, you can simply disable the exception with:

Private Sub DoStuff()
    Me.CheckForIllegalCrossThreadCalls = False
    Me.Text = "Stuff"
End Sub

This, of course, doesn’t prevent your code from making a mess. To do it properly, you need a delegate:

thread = New System.Threading.Thread(AddressOf DoStuff)
thread.Start()
Private Delegate Sub DoStuffDelegate()
Private Sub DoStuff()
    If Me.InvokeRequired Then
        Me.Invoke(New DoStuffDelegate(AddressOf DoStuff))
    Else
        Me.Text = "Stuff"
    End If
End Sub

Wildly simple right?

To understand what the code does, you need to understand what Invoke() does. It can be confusing to google Invoke() because Delegates and Controls have different implementations of Invoke(). In this case, we’re calling Invoke on a subclass of Control. That causes the method on the second thread which is going through DoStuff() to run a method on the first thread (which created the UI element).

Now if you need parameters, here’s another example where “ReallyLongProcess” is a subroutine that raises an event “Done” with a parameter “success”

AddHandler Me.Done, AddressOf WorkFinished
thread = new System.Threading.Thread(AddressOf ReallyLongProcess)
thread.Start()
Private Delegate Sub DoStuffDelegate(ByRef success as Boolean)
Private Sub DoStuff(ByRef success as Boolean)
    If Me.InvokeRequired Then
        Me.Invoke(New DoStuffDelegate(AddressOf DoStuff), success)
    Else
        If success Then
            'do stuff
        Else
            'do stuff
        End If
    End If
End Sub
  • Pingback: Multi-Threading and Delegates in VB .NET | Xster.net

  • Jalgaonkar Dewang

    Thank You Very Much For The Post…
    That really helped a lot!!!

  • Amila

    Thank Dear, this is very usefully for me. you saved my time.

  • Kevin

    Following the steps mentioned by the non-lazy way of do it, I make a thread.start on my form_load event to call the function CheckForUpdates().

    The problem I have is that the GUI is still not being refreshed as my threaded function works. My form consists of a progressbar, a picturebox and 2 buttons. If I don’t use threads at all, the form appears after CheckForUpdates(). If I use the non-lazy method, all my controls appear white and after CheckForUpdates() finishes, the controls are rendered properly.

    Any lead I’m missing here?
    Thanks!

  • Chris

    Thaaaaaaaaaaaaaaaaaaaaanks man. OMG i was looking for a solution and it was so simple

  • Kash

    Hi,
    The InvokeRequired and Invoke show error saying that they are not members of the UI Control ..
    What can be the problem?

  • http://tech.xster.net/ xster

    To clarify, the class that surrounds this whole thing is some type of Control such as a form or a user control. If you’re doing this on a basic library type simple object, there’s no need for invoke since you’re not updating the UI. If your simple object wants to cause UI in another class to update UI elements, have the invoke in the UI class.

  • Masika Bah Thaddeus

    I declared a Variable vElementNode as treenode in module and assigned it to a Treeview control
    How do I use the vElementNode in another thread?

  • mohamed yousef

    i am not expert in threading, i am doing the following:

    1 – open login screen

    2 – when user click login, i hide the login form then i show the WPF splash screen

    3 – a backgroundworker goes to check if his login data is right.

    4 – in case of any exception, or wrong entered credentials, i need to close the WPF splash screen the show the login screen once again.

    it’s been 2 days searching trying and i couldn’t do that.

    here is my code:

    LOGIN FORM:

    Imports MySql.Data.MySqlClient
    Imports System.Threading

    Public Class Login_Form

    Public shapedform As New WpfApplication1.MainWindow
    Dim CountOfRows As Integer
    Dim trd As System.Threading.Thread

    Private Sub Loginer_Click(sender As System.Object, e As System.EventArgs) Handles Loginer.Click

    Me.Visible = False
    shapedform.Show()

    Control.CheckForIllegalCrossThreadCalls = False
    BackgroundWorker1.RunWorkerAsync()

    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Call ConMe()
    SqlCommand = New MySqlCommand(“SELECT * FROM md_users where User_ID='” & Login_UserID.Text & “‘ and User_Password='” & Login_Password.Text & “‘”, Con)

    Try

    CountOfRows = Integer.Parse(SqlCommand.ExecuteScalar)

    Catch ex As Exception

    MessageBox.Show(ex.Message, “Err_Code: 002″, MessageBoxButtons.OK, MessageBoxIcon.Error)
    Me.Visible = True
    End Try

    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

    If CountOfRows = 1 Then
    Me.Close()
    MDI_Form.Show()
    Else
    Me.Visible = True
    End If

    End Sub

    Private Sub Canceler_Click(sender As System.Object, e As System.EventArgs) Handles Canceler.Click
    Me.Close()
    Me.Dispose()
    End Sub
    End Class

    ================

    Connecting MODULE:

    Imports MySql.Data.MySqlClient
    Imports System.Threading

    Module Connecting

    Public Con As MySqlConnection
    Public SqlCommand As MySqlCommand
    Public Reader As MySqlDataReader

    Sub ConMe()

    Con = New MySqlConnection(“Server=192.185.11.82;Port=3306;Database=eliteadm_Groups_Seating;Uid=elite_Admin;Pwd=@dm!n!strator123;”)

    If Con.State 1 Then

    Try
    Con.Open()
    Catch ex As Exception

    Dim workerThread As System.Threading.Thread = New Thread(Sub() DoStuff())
    workerThread.SetApartmentState(ApartmentState.STA)
    workerThread.Start()
    workerThread.Join()

    MessageBox.Show(ex.Message, “Err_Code: 001″, MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Try

    End If

    End Sub

    Private Delegate Sub DoStuffDelegate()

    Private Sub DoStuff()

    If Login_Form.InvokeRequired Then
    Login_Form.Invoke(New DoStuffDelegate(AddressOf DoStuff))
    Else
    Login_Form.shapedform.Hide()
    End If

    End Sub
    ‘Private Sub DoStuff()
    ‘ Control.CheckForIllegalCrossThreadCalls = False
    ‘ Login_Form.shapedform.Close()
    ‘End Sub

    End Module