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

29,332

Solution 1

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.

Solution 2

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.

Share:
29,332
Finch042
Author by

Finch042

Updated on January 19, 2020

Comments

  • Finch042
    Finch042 over 4 years

    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.

  • Finch042
    Finch042 over 10 years
    Mostly just wanted to give the simplest example I could find (using some code I came across earlier, while searching for an answer earlier). This is exactly what I needed and hopefully I should be able to adapt it accordingly. Thanks very much!
  • Finch042
    Finch042 over 10 years
    Odd the first I've heard of this, and it may end up helping me out QUITE a bit with the actual project my question regards. I'm using threadpool along with synclock to gather information about files and write it to a text file. Long story short, the threads don't necessary return in the order I need to write the information to the text file, and I wasn't really sure of a more elegant way to deal with it than Monitor.Wait/Minitor.Pulse etc. It might take me a while, but the link you provided seems like it might be faster/better. If you do have the time, I'd genuinely appreciate some guidance.
  • Finch042
    Finch042 over 10 years
    Either way, even if you don't end up getting around to anything else, thanks very much for the additional information/alternative.
  • jcwrequests
    jcwrequests over 10 years
    You might also like to check out Reactive Extensions jensbits.com/2012/01/23/…. I have been using TPL and RX quite extensively in projects and it alows me to write more declarative code. I will try and put a blog entry together or example on github. On github I am at github.com/jcwrequests or my blog is at wysnet.blogspot.com/. If you have a github account and post some questions and examples there I can always do a pull request with my responses. Any way good luck.