Introduction

I was just reading Will Green’s (Hotgazpacho) blogpost on using the RX extensions with winforms. And there he references Asynchronous Control Updates In C#/.NET/WinForms by Derrick Bailey. And I swear I never read about that trick (or more likely, I forgot. So I write about it here so I will not forget (yeah right).

Since we are still using threads and not the Async-Await keywords which will make these things go away we still have to write cross thread safe code in winforms.

bad code

Here is an example of code that will not work.

Imports System.Threading

Public Class Form1
    Private _counter As Integer

    Private Sub StartCounting()
        Dim timer1 As New Threading.Timer(New TimerCallback(AddressOf UpdateTextBox), Nothing, 1, 5000)
    End Sub

    Private Sub UpdateTextBox(ByVal obj As Object)
        _counter += 1
        TextBox1.Text = _counter.ToString()
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        StartCounting()
    End Sub
End Class

You will get this exception.

Good ugly code

We can fix the above by adding a bit of magic.

Imports System.Threading

Public Class Form1
    Private _counter As Integer

    Private Sub StartCounting()
        Dim timer1 As New Threading.Timer(New TimerCallback(AddressOf UpdateTextBox), Nothing, 1, 5000)
    End Sub

    Private Sub UpdateTextBox(ByVal obj As Object)
        If Me.InvokeRequired Then
            Invoke(Sub(x) UpdateTextBox(obj), obj)
        Else
            _counter += 1
            TextBox1.Text = _counter.ToString()
        End If
        
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        StartCounting()
    End Sub
End Class

The problem here is that you will have to repeat that a lot. And you seem to forget how to do that every time you need it. So let’s move on to something a little simpler.

The better code

Imports System.Threading
Imports System.Runtime.CompilerServices

Public Class Form1
    Private _counter As Integer

    Private Sub StartCounting()
        Dim timer1 As New Threading.Timer(New TimerCallback(AddressOf UpdateTextBox), Nothing, 1, 5000)
    End Sub

    Private Sub UpdateTextBox(ByVal obj As Object)
        TextBox1.DoCrossThreadCall(Sub(x)
                                       _counter += 1
                                       x.Text = _counter.ToString()
                                   End Sub)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        StartCounting()
    End Sub
End Class

Public Module ControlExtensions

    <Extension()>
    Public Sub DoCrossThreadCall(Of TControl As Control)(ByVal control As TControl, ByVal controlAction As Action(Of TControl))
        If control.InvokeRequired Then
            control.Invoke(controlAction, control)
        Else
            controlAction(control)
        End If
    End Sub
End Module

I can now use that extension everywhere I need it and keep the other code cleaner. The extension method is called DoCrossThreadCall just in case you didn’t notice.

Conclusion

This extension method makes your code a whole lot cleaner. But if you have a chance you should use the Async-Await keywords in the next version of .net since that will take care of that problem all by itself.