3

This is my situation, there are 2 Classes and my main form Form1:

Class1: has a method doSomethingAndCall(callback) which creates a new thread Class2: has dynamic created controls with a button that fires Class1.doSomethingAndCall(newCallback)

in code it looks like this (it starts at Class2.Button_Click):

Class Class1
  public shared sub doSomethingAndCallAsync(state as object)
    Console.WriteLine(Form1.InvokeRequired) 'output: false
    Console.WriteLine(Form1.IsHandleCreated) 'output: false
    Form1.Invoke(state.callback) 'throws System.InvalidOperationException
  end sub

  public shared sub doSomethingAndCall(callback as object)
    System.Threading.ThreadPool.QueueUserWorkItem(AddressOf doSomethingAndCallAsync, New With {.callback = callback})
  end sub
End Class

Class Class2
  Public Delegate Sub doSomethingDelegate()

  Public Sub doSomething()
    Console.WriteLine("success!")
  End Sub

  Public Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Class1.doSomethingAndCall(New doSomethingDelegate(AddressOf doSomething))
  End Sub
End Class

The exact exception I get is:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created

and as I can see the console.WriteLine in line 4 shows me that the form is realy not created. So I added this handlers, and now it get's really confusing:

 Private Sub Form1_HandleCreated(sender As Object, e As System.EventArgs) Handles Me.HandleCreated
        Console.WriteLine("Handle created") 'Output: Handle created, when running program
  End Sub

  Private Sub Form1_HandleDestroyed(sender As Object, e As System.EventArgs) Handles Me.HandleDestroyed
        Console.WriteLine("Handle destroyed") 'Will never Output!
  End Sub

So it's created and never destroyed but if i click the button it's nevertheless not avaible? -Can anyone explain me what is going on and how to call a callback correct, thanks!

MaBi
  • 1,372
  • 3
  • 13
  • 19
  • what is state.callback, what does it do? – Derek Sep 03 '14 at 13:00
  • Are you sure you're using an instance of Form1? It looks like you're saying `Form1.Invoke` on a type, not an instance of that type. – JoelC Sep 03 '14 at 13:11
  • @Derek: when working with threadpool, you can pass only 1 argument to the thread (as object) this is the state. But if you have to pass more than 1 argument, you can do something like: New With {.callback = callback}. So can pass an huge amount arguments. In my case the callback is New doSomethingDelegate(AddressOf doSomething), which I want to get called in doSomethingAndCallAsync. If you or somebody knows a better way please tell me :) – MaBi Sep 03 '14 at 13:27
  • possible duplicate of [Invoke or BeginInvoke cannot be called on a control until the window handle has been created](http://stackoverflow.com/questions/808867/invoke-or-begininvoke-cannot-be-called-on-a-control-until-the-window-handle-has) – Bjørn-Roger Kringsjå Sep 03 '14 at 13:30
  • @JoelC: Wouldn't it crashes in doSomethingAndCallAsync (when accessing Form1.InvokeRequired on a none instance). Form1 is my main form it contains all controls. – MaBi Sep 03 '14 at 13:35
  • @Bjørn-RogerKringsjå: I know this post, but according to the bold part in the answer: `"if the control was created on a different thread but the control's handle has not yet been created."` that doesn't really match to mine. Because the controls are dynamicly created and **then** I click on the button which starts the new thread, or did I missunderstood that? – MaBi Sep 03 '14 at 13:40
  • 3
    You'll have to wean yourself off the rather disastrous VB.NET feature that turns a *type name*, like Form1, into an object reference. That stops working when you do that in code that runs on another thread. It creates a *new* instance of Form1. One that you cannot see and is not suitable for Begin/Invoke calls. – Hans Passant Sep 03 '14 at 15:24
  • @HansPassant: Thanks for that comment! So is it correct to access Form1 like that: My.Forms.Form1? Because it will also throw the same exception, actually I could solve the problem now because of you and Bjørn-Roger Kringsjå, by giving Form1 as argument to the thread and calling invoke on it. But I'm also interested in other possible solutions, like just calling My.Forms.Form1 in the thread and accessing invoke on it (would be easier), but seems not to work – MaBi Sep 03 '14 at 17:28

1 Answers1

6

The instance of My.Forms.Form1 aka. Form1 will be different in each thread. You need a handle to the correct instance. Drop a button onto your Form1 and add the following code:

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Wrong())
        Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Correct(Me))
    End Sub

End Class

Public Class Class1

    Public Shared Sub Wrong()
        Debug.WriteLine(String.Format("(Other thread, wrong) InvokeRequired={0}, IsHandleCreated={1}", Form1.InvokeRequired, Form1.IsHandleCreated))
    End Sub

    Public Shared Sub Correct(instance As Form1)
        Debug.WriteLine(String.Format("(Other thread, correct) InvokeRequired={0}, IsHandleCreated={1}", instance.InvokeRequired, instance.IsHandleCreated))
    End Sub

End Class

Output

(Other thread, correct) InvokeRequired=True, IsHandleCreated=True

(Other thread, wrong) InvokeRequired=False, IsHandleCreated=False

Bjørn-Roger Kringsjå
  • 9,849
  • 6
  • 36
  • 64
  • 1
    thanks the solution "pass form1 as parameter" worked for me. Is there a reason you used Threading.Tasks.Task.Factory.StartNew (MSDN: Creates and starts a task.) instead of ThreadPool.QueueUserWorkItem (MSDN: Queues a method for execution) currently I'm thinking (and tested it) they are doing quite the same, or? – MaBi Sep 03 '14 at 18:39
  • 1
    @MaBi The reason as to why I use `Task` is simply because it's a newer and more powerful tool. You might want to read this SO post: [ThreadPool.QueueUserWorkItem vs Task.Factory.StartNew](http://stackoverflow.com/questions/9200573/threadpool-queueuserworkitem-vs-task-factory-startnew). Note Jon's (accepted answer) conclusion where he reiterate what the CLR team’s ThreadPool developer has already stated: *"Task is now the preferred way to queue work to the thread pool."* – Bjørn-Roger Kringsjå Sep 04 '14 at 05:49
  • I found this question when abstracting some code in my form to a dedicated "business logic" class, and (to my ignorance) I had blindly prefixed all variable calls of the form with the form's name. Well, since the code was executing on a different thread, AFAICT it was spawning at least one additional form instance that wasn't shown on the screen, hence the Invoke error. The solution I found was to create a Public `frmInstance` member of type `MyForm` and save the form instance in the given variable prior to running `Application.Run(frmInstance)`, and update all calls to use this variable. – Chad Dec 05 '19 at 16:34