Finch042
Finch042

Reputation: 307

VB.net, Invoke, delegates, and threading. Can't figure out how to use them across classes

Long story short, I'm having a hell of a time trying to figure out how to use invoke and/or delegates to update the userform from a separate class when using threading. I'm quite sure it's something silly and obvious to someone with more experience. I know a delegate is probably required, but all my efforts seem to only work when it's being called from main thread. I've been looking around the internet for half the day, and there's just something I'm not getting.

Here's some pseudo-code as an example:

This option works:

Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim t1 As New Threading.Thread(AddressOf Count)

    t1.IsBackground = True

    t1.Start(100)
End Sub
Private Sub Count(ByVal Max As Object)
    If TypeOf Max Is Integer Then
        Count(CInt(Max))
    End If
End Sub


Private Sub SetLabelText(ByVal text As String)
    If Label1.InvokeRequired Then
        Label1.Invoke(New Action(Of String)(AddressOf SetLabelText), text)
    Else
        Label1.Text = text

    End If
End Sub

Private Sub Count(ByVal Max As Integer)
    For i = 1 To Max
        SetLabelText(CStr(i))
        Threading.Thread.Sleep(200)
    Next
End Sub
End Class

While this (one of my 1000 efforts of slightly different variation) does not. Practically speaking, I just tried to separate one of the subs into its own class for this example, but it's otherwise the same as I could make it:

Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim t1 As New Threading.Thread(AddressOf Count)
    t1.Start(100)

End Sub
Private Sub Count(ByVal Max As Object)
    If TypeOf Max Is Integer Then
        Dim class2 As New class2
        class2.Count(CInt(Max))
    End If
End Sub

Private Delegate Sub SetTextBoxTextInvoker(text As String)
Sub SetLabelText(ByVal text As String)
'or me.label1, form1.label1 or anything else I can try!
    If Me.InvokeRequired Then
        Me.Invoke(New SetTextBoxTextInvoker(AddressOf SetLabelText), _
               text)
    Else
        Me.Label1.Text = text
    End If
End Sub
End Class

Public Class class2
Sub Count(ByVal Max As Integer)
    For i = 1 To Max
        form1.SetLabelText(CStr(i))
        Threading.Thread.Sleep(200)
    Next
End Sub
End Class

From what I can tell, it appears that the if statement for invokerequired in the Sub "SetLabelText" never gets triggered. My best guess is that I'm not referring to the userform correctly when checking for the invokerequired parameter? Or I need to feed something else to the delegate? I'm just getting frustrated with messing around with the million little variables I might be getting wrong. Thanks in advance for any help you can provide and let me know if you need more info.

Upvotes: 3

Views: 30006

Answers (2)

jcwrequests
jcwrequests

Reputation: 1130

First off I would suggest using the Task Parrallel Library instead of threads. It's easier to understand and work with. For example,

Dim countTask as New Task(Sub() Count(10))

Dim displayTask = countTask.ContinueWith(Sub()
                                           Me.Invoke(Sub() Label.Text = "10"

                                         End Sub)

 countTask.Start()

This example assumes you are calling this from the form itself. You can use this method to return values from the first task to the second. If you need a more detail example and want more examples check out http://msdn.microsoft.com/en-us/library/hh228603.aspx. If you need further help I can always throw something up on GitHub or blog about it. Good Luck.

Upvotes: 2

Jay Taplin
Jay Taplin

Reputation: 570

I'm not certain I understand what you are trying to do, but building upon your code, you can set the label safely ("thread-safely") by using the following code:

Public Class Form1
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim t1 As New Threading.Thread(AddressOf Count)

        t1.IsBackground = True

        t1.Start(100)
    End Sub

    Private Sub Count(ByVal Max As Object)
        If TypeOf Max Is Integer Then
            Dim class2 As New Class2

            class2.Count(CInt(Max), AddressOf SetLabelText)
        End If
    End Sub

    Private Sub SetLabelText(ByVal text As String)
        If Label1.InvokeRequired Then
            Label1.Invoke(New SetText(AddressOf SetLabelText), text)
        Else
            Label1.Text = text
        End If
    End Sub
End Class

Public Class Class2
    Sub Count(ByVal Max As Integer, SetTextMethod As SetText)
        For i = 1 To Max
            SetTextMethod.Invoke((CStr(i)))

            Threading.Thread.Sleep(200)
        Next
    End Sub
End Class

Public Delegate Sub SetText(text As String)

I created a Delegate called "SetText"; when the form calls the count function in your class, you can pass an instance of the delegate that references the SetLabelText method. Within that method you can then safely set the label text either directly or indirectly via Invoke along with a new instance of the delegate.

Something you definitely don't want to do is reference your form from your class(i.e. "form1.SetLabelText(CStr(i))"); that can create a real nightmare as the project grows in size and requirements change!

If I've misunderstood your question or not answered it properly, please do post back.

Upvotes: 2

Related Questions