0

I have probably read and tried a hundred different ways to do this, and I can't take it any longer or wrap my head around it. I am new to multi-threading and usually make the UI process the code. I found the error of my ways as my program grew. Now I have classes and a thread that does not lag-out/freeze the main UI.

From within that class I am trying to update a label on the main form. (with what progress has been made)

The relevant code is as follows:

There is a label on the main form called UpdateLabel

A button on the main form:

Private Sub btnStartMenu_Click(sender As Object, e As EventArgs) Handles 
btnStartMenu.Click
    Call New Action(AddressOf setupthread1).BeginInvoke(Nothing, Nothing)
End Sub 'creates a new thread which runs great, not freezing the UI!


Private Sub setupthread1()    'this code is still on the main form
                              'all of the label updates work fine from here.
 StartMenu_Folders.startMenuFolders()  'This is the class that gets called 
                                       'from the main form

Public Class StartMenu_Folders
    Public Shared Sub startMenuFolders()
              frmMenu.UpdateLabel(frmMenu.lblLogoff, "...Updating Label text")    'this code is probably incorrect? Though the updatelabel DOES get 
       'successfully called



'This next part is back on the main form.
Public Sub UpdateLabel(ByVal lblLogoff As Label, ByVal Value As String)
    If lblLogoff.InvokeRequired Then
        Dim dlg As New UpdateLabelDel(AddressOf UpdateLabel)
        dlg.Invoke(lblLogoff, Value)
    Else
        lblLogoff.Text = Value
    End If
End Sub

ANY help would be appreciated, it is probably a 2 second explanation from one of you senior coders out there.

At this point it looks like the threads are running perfectly with no freezing, but the label just is not updating. . Thank you. I really appreciate it!

========================== Update EDIT to Code from information provided.

From viewers like YOU! Cue Reading rainbow music

'This is all on Main form 'frmMenu'
'Improved way of starting the class in a new thread
Private Sub btnStartMenu_Click(sender As Object, e As EventArgs) Handles btnStartMenu.Click
    Dim t As Task = Task.Run(Sub()
StartMenu_Folders.startMenuFolders()
  End Sub)
End Sub

Public Delegate Sub UpdateLabelInvoker(ByVal text As String)

Public Sub UpdateLabel(ByVal text As String)
    If Me.lblLogoff.InvokeRequired Then
        Me.lblLogoff.Invoke(New UpdateLabelInvoker(AddressOf UpdateLabel), _
                           text)
        MsgBox("invoked")
    Else
        Me.lblLogoff.Text = text
        MsgBox("DIDNT invoke")
    End If
End Sub

    'This is all in the class

Public Class StartMenu_Folders
Public Shared Sub startMenuFolders()
frmMenu.UpdateLabel("testtestest")
End Sub
End Class
'This code is still creating a separate instance of frmMenu I believe.
'I'm not sure how to target the right thread to update the label. 
'I've tried me.UpdateLabel("testtesttest") to no avail.

If anyone knows how to do that, that would be great! Everything else is working swimmingly. It's a shame that updating a label on a form from another thread is the hardest hoop I've had to jump through for this program in the months I've been working on it.

user2261192
  • 13
  • 1
  • 6
  • Note that you don't need to define your own delegate. You should use `MethodInvoker` if there are no arguments and nothing to return or else use an appropriate `Action` or `Func`. I do use my own delegates in the examples in the thread I linked to but that's because `Action` and `Func` didn't exist back then. – jmcilhinney Jul 17 '18 at 03:24
  • If you are always going to be setting the `Text` of the same `Label`, why does the other form need to pass the `Label` and an argument? – jmcilhinney Jul 17 '18 at 03:26
  • Actually, you may well have another issue there too. Where you call `frmMenu.UpdateLabel`, is `frmMenu` a variable that refers to the existing instance or is it the type name and thus refers to the default instance? If it's the latter then that won't work because default instances are thread-specific. That means that that code would create a new instance of the form and modify that rather than modifying the existing one. – jmcilhinney Jul 17 '18 at 03:41
  • @jmcilhinney I actually thought I was supposed to use me.UpdateLabel but it wasn't allowing me to do that so I had added the frmMenu.UpdateLabel and it would actually fire the UpdateLabel. – user2261192 Jul 17 '18 at 03:50
  • @jmcilhinney The text of the label changes as progress is made in my different classes. I was trying to have each class pass an argument to the main form to update the label. I am very very new at multi-threading/as you can see. I'm still wrapping my head around how to use delegates and such. – user2261192 Jul 17 '18 at 03:57
  • 1
    If you follow the link I provided in my answer then that will cover the delegates. The thing is, you need to actually call the `UpdateLabel` method on the instance of `frmMenu` that you want to affect, which you currently are not doing. You can learn something about default instances [here](http://jmcilhinney.blogspot.com/2009/07/vbnet-default-form-instances.html). – jmcilhinney Jul 17 '18 at 08:38
  • In addition to what jmcilhinney has already told you about default instances, you need to pass the current instance of your form (`Me`) as an argument to the thread to be able to use it _in the thread_. – Visual Vincent Jul 17 '18 at 10:20
  • Also, I highly recommend you get rid of `Action.BeginInvoke` and instead use a [**`Task`**](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx) to start your thread. The reason for this is that a call to `Action.BeginInvoke` must be followed by a call to `Action.EndInvoke` (on the very same `Action` instance!) when the work is done, otherwise you'll end up having memory/resource leaks (see: https://stackoverflow.com/a/11620700). Using a `Task` you don't have to think about stuff like that. – Visual Vincent Jul 17 '18 at 10:21
  • @VisualVincent So I would need: Imports System.Threading.Tasks Dim t As Task = Task.Run(sub() StartMenu_Folders.startMenuFolders() End Sub) – user2261192 Jul 17 '18 at 12:16
  • Something like that, yes. Then you can pass your form to it by doing: `StartMenu_Folders.startMenuFolders(Me)` – Visual Vincent Jul 17 '18 at 12:22
  • @VisualVincent I've edited the code to follow your guidelines and avoid memory leaks in the future. Thank you! – user2261192 Jul 18 '18 at 01:09

1 Answers1

3

You don't call Invoke on the delegate and pass the Label. You call Invoke on the Label and pass the delegate:

lblLogoff.Invoke(dlg, Value)

See my explanation and examples here.

EDIT:

OK, I think I've got a handle on it this time. This:

Public Class StartMenu_Folders
    Public Shared Sub startMenuFolders()
              frmMenu.UpdateLabel(frmMenu.lblLogoff, "...Updating Label text")    'this code is probably incorrect? Though the updatelabel DOES get 
       'successfully called

becomes this:

Public Class StartMenu_Folders

    Public Shared Sub startMenuFolders(menuForm As frmMenu)
        menuForm.UpdateLabel("...Updating Label text")

This:

StartMenu_Folders.startMenuFolders()

becomes this:

StartMenu_Folders.startMenuFolders(Me)

This:

Public Sub UpdateLabel(ByVal lblLogoff As Label, ByVal Value As String)
    If lblLogoff.InvokeRequired Then
        Dim dlg As New UpdateLabelDel(AddressOf UpdateLabel)
        dlg.Invoke(lblLogoff, Value)
    Else
        lblLogoff.Text = Value
    End If
End Sub

becomes this:

Public Sub UpdateLabel(ByVal Value As String)
    If lblLogoff.InvokeRequired Then
        Dim dlg As New UpdateLabelDel(AddressOf UpdateLabel)
        lblLogoff.Invoke(dlg, Value)
    Else
        lblLogoff.Text = Value
    End If
End Sub

In future, please don't post code from two different classes in the same block as it has caused confusion here. If you have two classes then post code from them in two different blocks.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • The lblLogoff.Inoke(dlg, Value) line doesn't fire, the code actually jumps to the 'else' section of just trying to change the text. Does this have to do with a new instance being created of the frmMenu.UpdateLabel? – user2261192 Jul 17 '18 at 03:54
  • Yes it does. I wrote this answer before writing that comment. What I've provided in this answer is correct in that you do need the code this way but you've got to overcome the other problem first. If you use the default instance on the secondary thread then it creates a new instance on that that. Because that instance is owned by that thread, `InvokeRequired` is `False`. The `Label` would get updated but on the wrong form; one you can't see because it was never displayed. – jmcilhinney Jul 17 '18 at 08:34
  • Thanks, I'm going to try and post my updated code. The link you had provided I had tried a week ago for a long time. I feel like i've gone through every search there was to get this to work. I will try it again. – user2261192 Jul 17 '18 at 12:04
  • Thank's for the guides. I've updated the code to look more like the ones you had provided. I still am having that pesky issue with the new instance on the secondary thread being created. Any ideas? – user2261192 Jul 18 '18 at 01:08
  • You can't use the default instance, plain and simple. If the dialogue needs to access a member of the calling form then the calling form pass a self-reference, i.e. `Me`, to the dialogue. The dialogue can store that reference in a field and use it later. As this reference is required, it makes sense to make it a constructor parameter, so you can't create a dialogue instance without it. I'll add a quick code example of that. – jmcilhinney Jul 18 '18 at 01:23
  • Thank you for the edit. I'm still a little confused... is there anyway you could explain this using my variable names? I'm not sure which is which with your example. I don't mean to sound like a newbie, I just really am new to the terminology you're using, and learn through viewing working code best. Should I no longer use the code you had referenced on your vbforums link? Just really at my wits end with something that should be so simple. I only have one form also, so that makes this edit a little more confusing for me. – user2261192 Jul 18 '18 at 02:16
  • "Should I no longer use the code you had referenced on your vbforums link". Yes you should. As I already said, that correction is required but you need to fix this other issue too before it will be useful. "I only have one form". Well, that makes a significant difference. In that case, why are you calling `UpdateLabel` on anything other than `Me`? If what you're trying to do is call a method in the current object then you just call a method in the current object, just like you would any other time. – jmcilhinney Jul 18 '18 at 02:25
  • In fact, the other thread I linked to solves the first problem too. If you look at the first post, the code on the secondary thread is calling `Me.ResetTextBoxText()`. It's not calling `Form1.ResetTextBoxText()` because it's on a secondary thread. It's calling a method of the current of object and it does so exactly the same way as it would if was executed on the UI thread. – jmcilhinney Jul 18 '18 at 02:28
  • So that's why originally I had commented up above " I actually thought I was supposed to use me.UpdateLabel but it wasn't allowing me to do that so I had added the frmMenu.UpdateLabel and it would actually fire the UpdateLabel" I get a " 'Me' is valid only within an instance method." If I remove the frmMenu and change it to Me.UpdateLabel This is why I am so confused. Is it because it is within a class? I understand now the UpdateLabel is firing, but it is a new instance of it. ... I think? Sorry reading your edited edit edit now. – user2261192 Jul 18 '18 at 02:38
  • "UpdateLabel is not declared. It may be inaccessible due to its protection level" hrmm – user2261192 Jul 18 '18 at 02:48
  • OK, so you don't have two forms but you do have two classes. `StartMenu_Folders` is the class that is calling the method on the form so that is the class that the form must pass a self-reference into. What I said before still applies: you can't use the default instance on a secondary thread because it will be a different instance. Your `StartMenu_Folders` class has to call a method on the existing form object so it needs a reference to that existing form object, which you need to pass in somehow. That might be using a `Shared` property, given that the method you're calling is `Shared`. – jmcilhinney Jul 18 '18 at 02:51
  • I guess that's been the meat of my question is how to do that. This is probably why all of the examples I have followed haven't been working. – user2261192 Jul 18 '18 at 03:08
  • You are a gentleman and a scholar! (and maybe a robot) You never sleep, and helped me complete this part of my program that I struggled with and used up all resources I had already known about. Thank you for your service, it is VERY much appreciated. It works perfectly now and I can take it from here. (read: I will message you tomorrow about something else) Thank you again! – user2261192 Jul 18 '18 at 22:05