One for the multithreading Gurus

  • Thread starter Thread starter dgleeson3
  • Start date Start date
D

dgleeson3

Hello All

Im having lots of fun with window handles and invoke.

The code started off in a single class. Main thread set up a worker
thread and the worker thread updated the progress bar on form1 (which
only has a progress bar and a button). All worked well - No problems.

The implementation then changed to 2 classes. This code is given
below.

First problem was with beginInvoke. An exception was being generated
saying that
"Invoke or BeginInvoke cannot be called on a control until the window
handle has been created."
So to ensure I had a handle to the progress bar I simply referenced
the handle in the constructor see
winhandle = ProgressBar1.Handle
This solved this problem. Though Im not sure whats going on.


Now Im on the next problem. When BeginInvoke is called as in
" result = Form1.ProgressBar1.BeginInvoke(New
InvokeDelegate(AddressOf Form1.UpdateProgressDisplay), 25)"
and when Im debugging the code I find myself stepping through the
constructor for Form1 again. Its like as if the form1 object has been
removed and a reference to it is causing it to be created again.

Iv tried " Public Shared ProgressBar1 As New ProgressBar" in an
effort to see if the form1 object is being removed. And ive passed the
form1 to the second class in its constructor. All to no avail.


Any Gurus able to point me in the right direction.



-----------------------------------------------------------------------------------------------------------
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Threading
Imports System.IO


Public Class Form1
'Dim Communications_thread As New Thread(AddressOf
DoRemoteCommunications)
Dim worker As System.Threading.Thread ' Thread
Dim worker_obj As New worker_class(Me) ' Object
from worker class
' Set up delegate for assync function call.
--------------------------------
'Public Delegate Sub Async_Update_Progress_caller(ByVal
Value_for_progress_bar As Integer)
Public Shared ProgressBar1 As New ProgressBar
'Delegate Sub InvokeDelegate()
Public winhandle As IntPtr

Public Sub New()
Me.InitializeComponent()
'ProgressBar1
'
Me.SuspendLayout()

ProgressBar1 = New System.Windows.Forms.ProgressBar

ProgressBar1.Name = "ProgressBar1"
ProgressBar1.Maximum = 100
ProgressBar1.Value = 10
ProgressBar1.Location = New System.Drawing.Point(69, 120)
ProgressBar1.Size = New System.Drawing.Size(145, 23)
ProgressBar1.TabIndex = 0
' To have handle for progress bar available. Othyerwise
beginInvoke fails because handle is not available.
' I think this may actually be creating the handle, which may
not exist otherwise.
winhandle = ProgressBar1.Handle

Me.Controls.Add(ProgressBar1)

Me.ResumeLayout(False)

End Sub

Public Sub UpdateProgressDisplay(ByVal Value_for_progress_bar As
Integer)

ProgressBar1.Value = Value_for_progress_bar

End Sub




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

worker = New Thread(AddressOf
worker_obj.DoRemoteCommunications)

' Now actually start the comms thread.
worker.Start()
End Sub


End Class

Public Class worker_class

Delegate Sub InvokeDelegate(ByVal temp As Integer)
Public form_reference As Form

Public Sub New(ByVal form_ref)
form_reference = form_ref
End Sub

Public Sub DoRemoteCommunications()
Console.WriteLine("Comms worker thread started.....")

' Initiate the asynchronous call.
Dim result As IAsyncResult

result = Form1.ProgressBar1.BeginInvoke(New
InvokeDelegate(AddressOf Form1.UpdateProgressDisplay), 25)
Console.WriteLine("Progress value 25 passed ")
Thread.Sleep(3000)

result = Form1.ProgressBar1.BeginInvoke(New
InvokeDelegate(AddressOf Form1.UpdateProgressDisplay),
Form1.ProgressBar1.Maximum)
Console.WriteLine("Comms worker thread Finished.")

End Sub



End Class


-----------------------------------------------------------------------------------------------------


Thanks

Denis

____________________
http://www.CentronSolutions.com
 
Hello All. Im having lots of fun with window handles
and invoke.

Welcome to the imposter.
The code started off in a single class. Main thread set
up a worker thread and the worker thread updated . . .

Welcome to the imposter.
First problem was with beginInvoke. An exception
was being generated saying that "Invoke or . . "

Welcome to the imposter.
Any Gurus able to point me in the right direction.

Yes. Have a look for newsgroups that deal with the imposter. This group is
for the *real* Visual Basic.

Mike
 
As Mike said (cryptically), this should be in a .Net newsgroup, so leave off the
microsoft.public.vb.general.discussion.

You need to understand forms and classes and instances better before you tackle
worker threads. You should probably change your worker class to work within the
same thread first, and get the form reference all squared away, before doing a
worker thread and invoking a method in the main thread.
' I think this may actually be creating the handle, which may
not exist otherwise.
winhandle = ProgressBar1.Handle
Dim worker_obj As New worker_class(Me)

Creating a new instance of a form does *not* load it, and therefore does *not*
create the windows, or handles to those windows. Only when the form loads are
those available. You are forcing the form to load in the constructor, which is
daft. You think you need this because your attempt at invoking a method is
creating a second instance of Form1, which otherwise never gets loaded. See
below.

Dim worker_obj As New worker_class(Me)

Okay, your worker_obj instance of class worker_class gets passed a reference to
the actual instance of the calling form.
Public Class worker_class....
Public form_reference As Form

Why is form_reference of class Form, not class Form1? And why is it Public?
Public Sub New(ByVal form_ref)
form_reference = form_ref
End Sub

Okay, that will store a reference to the calling form, when an instance of
worker_class is created. It would be smarter to specify the type, not leave it
as Object.

Now that you have it, it seems like it would make sense to use form_reference
somewhere in your worker_class code, don't you think?
result = Form1.ProgressBar1.BeginInvoke(New

Having just saved a reference to the actual form instance, why are you now
referring to Form1? Form1 is a class name. It can also be a default instance
name, but often not the instance you want. The above line is triggering the
creation of a new (second) instance of Form1, not referring to the already
loaded instance that called this method.

Can you guess what you should put instead of Form1 in the line above?
 
[...]
First problem was with beginInvoke. An exception was being generated
saying that
"Invoke or BeginInvoke cannot be called on a control until the window
handle has been created."
So to ensure I had a handle to the progress bar I simply referenced
the handle in the constructor see
winhandle = ProgressBar1.Handle
This solved this problem. Though Im not sure whats going on.

Yes, retrieving the handle forces it to be created. However, it would be
better to just either not start whatever processing might call
BeginInvoke() until the form has been loaded and/or shown (see FormLoad
and FormShown events). Alternatively, the processing code calling
BeginInvoke() should just check the IsHandleCreated property before trying
to invoke and not bother if the control hasn't been fully initialized yet.

The way you're doing it now, you allow some update to be invoked on the
progress bar before the form is really completely initialized. By forcing
the progress bar itself to be initialized enough, you may avoid problems..
But there's no guarantee of that, and it could easily lead to a subtle,
hard to find bug later.
Now Im on the next problem. When BeginInvoke is called as in
" result = Form1.ProgressBar1.BeginInvoke(New
InvokeDelegate(AddressOf Form1.UpdateProgressDisplay), 25)"
and when Im debugging the code I find myself stepping through the
constructor for Form1 again. Its like as if the form1 object has been
removed and a reference to it is causing it to be created again.

Iv tried " Public Shared ProgressBar1 As New ProgressBar" in an
effort to see if the form1 object is being removed. And ive passed the
form1 to the second class in its constructor. All to no avail.

Do you have an example of the code that generates that behavior? The code
you posted is the version with the static/shared ProgressBar instance.

One thing I notice is that BeginInvoke() takes as the second parameter an
array, not a single value. I'm not that familiar with the VB syntax, and
if you're sure your code is correct then I'll take your word for it. But
what I expected to see there is you created a new Object() array with one
element, set to the value of 25 (which will wind up boxed, being a value
type).

If you have that parameter wrong, in C# you just wouldn't be able to
compile the code. But in VB maybe it compiles but then causes some weird
error.

The other thing I notice is that it _looks_ like you are calling an
instance method in your Form1 class (UpdateProgressDisplay), but using the
synax for a static/shared method. Again, not being that familiar with VB
syntax maybe I'm wrong, but I thought you'd have a "Shared" in the method
declaration if it were a static method. If it _is_ an instance method,
then maybe VB is creating a new dummy instance for you when you call
BeginInvoke(), since you didn't provide an instance.

Again, in C# you just wouldn't be able to do that. But maybe in VB it's
doing something on your behalf (scary, but possible).

Finally, you should look at the BackgroundWorker class. At least for this
simple kind of processing you've got here, it's exactly the right thing to
use. It has a ProgressChanged event that the Form1 class can subscribe
to, as well as a RunWorkerCompleted event. Both of these events will be
raised on the form instance's thread, eliminating any need for you to mess
with BeginInvoke() at all. It would eliminate the issues you've described
here.

For RunWorkerCompleted, it's raised automatically for you when your DoWork
handler returns. For the ProgressChanged event, your worker thread code
will need to call the ReportProgress() method any time you want the event
raised.

Pete
 
Mike Williams said:
Hello All. Im having lots of fun with window handles
and invoke.

Welcome to the imposter.
The code started off in a single class. Main thread set
up a worker thread and the worker thread updated . . .

Welcome to the imposter.
First problem was with beginInvoke. An exception
was being generated saying that "Invoke or . . "

Welcome to the imposter.
Any Gurus able to point me in the right direction.

Yes. Have a look for newsgroups that deal with the imposter. This group is
for the *real* Visual Basic.
[/QUOTE]


And he probably has no clue at all what you're talking about. <g>

To the OP, you cross-posted to both VB classic (VB6 and under) and .NET
newsgroups. Don't do this.
 
Please trim the group 'microsoft.public.vb.general.discussion' from
any replies you make to this thread

It's inappropriate for that group.

Thanks.
 
Hi Guys

Thanks for the input. Appologies to the VB6'rs

Pete responses below.


[...]
First problem was with beginInvoke. An exception was being generated
saying that
"Invoke or BeginInvoke cannot be called on a control until the window
handle has been created."
So to ensure I had a handle to the progress bar I simply referenced
the handle in the constructor see
winhandle = ProgressBar1.Handle
This solved this problem. Though Im not sure whats going on.

Yes, retrieving the handle forces it to be created. However, it would be
better to just either not start whatever processing might call
BeginInvoke() until the form has been loaded and/or shown (see FormLoad
and FormShown events). Alternatively, the processing code calling
BeginInvoke() should just check the IsHandleCreated property before trying
to invoke and not bother if the control hasn't been fully initialized yet.

The way you're doing it now, you allow some update to be invoked on the
progress bar before the form is really completely initialized. By forcing
the progress bar itself to be initialized enough, you may avoid problems.
But there's no guarantee of that, and it could easily lead to a subtle,
hard to find bug later.

So the worker thread is not setup until the user can press the button
on the form.
Long after form load. But Im now checking with IsHandleCreated. Very
strange!
Do you have an example of the code that generates that behavior? The code
you posted is the version with the static/shared ProgressBar instance.

One thing I notice is that BeginInvoke() takes as the second parameter an
array, not a single value. I'm not that familiar with the VB syntax, and
if you're sure your code is correct then I'll take your word for it. But
what I expected to see there is you created a new Object() array with one
element, set to the value of 25 (which will wind up boxed, being a value
type).

Ok Ive modified this setting up an array. No change in operation with
my problem.VB was
helping me on this.
If you have that parameter wrong, in C# you just wouldn't be able to
compile the code. But in VB maybe it compiles but then causes some weird
error.

The other thing I notice is that it _looks_ like you are calling an
instance method in your Form1 class (UpdateProgressDisplay), but using the
synax for a static/shared method. Again, not being that familiar with VB
syntax maybe I'm wrong, but I thought you'd have a "Shared" in the method
declaration if it were a static method. If it _is_ an instance method,
then maybe VB is creating a new dummy instance for you when you call
BeginInvoke(), since you didn't provide an instance.

Nice one.
This appears to be the problem.
Changing the method decleration to include "shared" removes the
behaviour where
the form1 object is being re created.

Public Shared Sub UpdateProgressDisplay(ByVal
Value_for_progress_bar As
Integer)

This is strange as Im not receiving even a warning during the build.
Multi threaded VB apps
may be a step too far. Might be time for C#.

Thanks for your help.

Denis
 
Back
Top