please help on multithreading

  • Thread starter Thread starter Sam
  • Start date Start date
S

Sam

Hi,
I have a serious issue using multithreading. A sample application
showing my issue can be downloaded here:
http://graphicsxp.free.fr/WindowsApplication11.zip

The problem is that I need to call operations on Controls from a
delegate, otherwise it does not work. However each time I've done an
operation, I must update the progressbar and progresslabel, but this
cannot be done in the delegate as it does not work.

In the sample I've just done a loop to increase the progressbar, but
obviously in reality the progressbar updates and the operations on
controls (tabcontro, datagrid..) should be nested, hence the issue!

Please have a look at the sample for a clearer idea of what i'm trying
to achieve.

Thanks for your help:)
 
Ken,
Thanks for the link but it's pretty much what i do already. please have
a look at my sample application. Can you just tell me if you see what's
wrong at first sight ?
thank you
 
Hello,
I am still struggling for fixing this one. Has someone looked at my
code?
I would be gratefull if someone could spot what I'm doing wrong.

Regards
 
Sam said:
Hi,
I have a serious issue using multithreading. A sample application
showing my issue can be downloaded here:
http://graphicsxp.free.fr/WindowsApplication11.zip

The problem is that I need to call operations on Controls from a
delegate, otherwise it does not work. However each time I've done an
operation, I must update the progressbar and progresslabel, but this
cannot be done in the delegate as it does not work.

In the sample I've just done a loop to increase the progressbar, but
obviously in reality the progressbar updates and the operations on
controls (tabcontro, datagrid..) should be nested, hence the issue!

Please have a look at the sample for a clearer idea of what i'm
trying to achieve.

Thanks for your help:)

I had a look at your project. Took some time to understand the code, and to
find the actual problem. I'm still not sure if I got it. What I can say for
sure is that all the code accessing a control must be done in the thread
that created the control. This is true for the progressbar but also for the
tabcontrol. If you access it from a different thread, it might happen to
work, but you shouldn't do it. Thus, always use Invoke/Begininvoke.

Does this help? Otherwise, please show me the steps that I can do in your
project to reproduce the problem. Although there are comments in it, I
currently don't know which part(s) I have to (un)comment to see whatever.

Armin
 
Armin,
Thank you for taking time for looking at my problem.
Regarding the fact that access to a control should only be done from
the thread that created the control, I know this and that's why I've
used delegate. The problem is that I don't use them the right way, at
least that's what I think....

Let's have a look at the following code:

Public Sub DoWork()

The following loop works fine and the progress/label control are
updated correctely. I believe this is because pb.InvokeRequired is True
in UpdateBar and same thing in UpdateLabel.

For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next

Here I call a delegate to UpdateUI
Invoke(New UpdateUIDelegate(AddressOf UpdateUI))

BeginInvoke(frmProgressRef._close)
End Sub

Public Sub UpdateUI()

The following loop however is wrong and the label in the progress form
is not updated properly. I believe this is because pb.InvokeRequired
is True in UpdateBar and same thing in UpdateLabel, although the
progressbar is updated correctely (I guess on another pc with different
OS/CPU it might not be...)

For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next
End Sub

So my question is: Why pb.InvokeRequired is False when the delegates
to UpdateLabel and UpdateBar are called from the delegate to UpdateUI ?

Hope this makes it clearer...
Thank you
 
Sam said:
Armin,
Thank you for taking time for looking at my problem.
Regarding the fact that access to a control should only be done from
the thread that created the control, I know this and that's why I've
used delegate. The problem is that I don't use them the right way,
at least that's what I think....

Let's have a look at the following code:

Public Sub DoWork()

The following loop works fine and the progress/label control are
updated correctely. I believe this is because pb.InvokeRequired is
True in UpdateBar and same thing in UpdateLabel.

No, the opposite is the case. It happens to work, but actually it is wrong.
InvokeRequired=True means that you must not access the control from the
thread that calls InvokeRequired. As the name says, Invoke is required to
access the control. If you do use Invoke, InvokeRequired returns false, in
the called procedure. This means that Invoke is not required anymore because
you're running in the correct thread and it is now safe to access the
control.
For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next

Here I call a delegate to UpdateUI
Invoke(New UpdateUIDelegate(AddressOf UpdateUI))

BeginInvoke(frmProgressRef._close)
End Sub

Public Sub UpdateUI()

The following loop however is wrong and the label in the progress
form is not updated properly. I believe this is because
pb.InvokeRequired is True in UpdateBar and same thing in
UpdateLabel, although the progressbar is updated correctely (I guess
on another pc with different OS/CPU it might not be...)

For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next
End Sub

So my question is: Why pb.InvokeRequired is False when the
delegates to UpdateLabel and UpdateBar are called from the delegate
to UpdateUI ?

It is False as explained above: It is not required to call Invoke, which
means you are running in the correct thread and it is safe to access the
controls.
Hope this makes it clearer...

A bit. :-) Call Me.label1.refresh in Sub UpdateLabel to update the label
immediatelly. Attention: In WinXP it is still not guarenteed that the label
is updated if the whole process (till 100% is reached) takes a long time.
There is no possibility to force the label to update itself. Not even
calling Refresh is save. See 3rd paragraph @
http://msdn.microsoft.com/library/e...ssagequeues/aboutmessagesandmessagequeues.asp

Work-around: Within the loop, call PeekMessage API function from time to
time.

This problem does not exist if you update the label from the wrong thread
because the UI thread has time to update the label.

I guess this is the problem. Let me know if I'm wrong.


Armin
 
Armin,
Thank you again for helping me.
Using the Refresh method on label1 has indeed updated the label and the
program works as expected :-)
However I do NOT understand at all what I'm doing. Doing your way
means my loop which was:

For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next

Can now become:

For i As Integer = 0 To 100
frmProgressRef.Label1.Text = i.ToString
frmProgressRef.Label1.Refresh()
frmProgressRef.ProgressBar1.Value = i
AsyncOpDone.WaitOne(10, True)
Next

This looks really strange to me as i'm using the control on frmProgress
without using delegates. It looks ugly, doesn't it ?

No, the opposite is the case. It happens to work, but actually it is wrong.
WInvokeRequired=True means that you must not access the control from the
thread that calls InvokeRequired

I swear to you, it works :-) If you launch my program you will see that
the loop in DoWork() works fine and yet it has InvokeRequired=True.
Therefore the solution you gave me do work, but i'm still confused.

Thanks.
 
Actually, if you don't mind, I have two more questions. I've updated my
sample application and made it clearer and easier to use, you can get
it here:

http://graphicsxp.free.fr/WindowsApplication11.zip

When I click on 'Load child form', the child form is not shown until
the progression is finished, and I don't understand why?

Then if you click on 'Click me', you will see the bug I describe on the
form (in a richtextbox) to make it easier to reproduce.

thanks
 
Sam said:
Armin,
Thank you again for helping me.
Using the Refresh method on label1 has indeed updated the label and
the program works as expected :-)
However I do NOT understand at all what I'm doing. Doing your way
means my loop which was:

For i As Integer = 0 To 100
UpdateLabel(frmProgressRef.Label1, i.ToString)
UpdateBar(frmProgressRef.ProgressBar1, i)
AsyncOpDone.WaitOne(10, True)
Next

Can now become:

For i As Integer = 0 To 100
frmProgressRef.Label1.Text = i.ToString
frmProgressRef.Label1.Refresh()
frmProgressRef.ProgressBar1.Value = i
AsyncOpDone.WaitOne(10, True)
Next

I don't see the difference concerning multithreading because both versions
actually do the same thing.
This looks really strange to me as i'm using the control on
frmProgress without using delegates. It looks ugly, doesn't it ?

The code is not ugly, but it's executed in the wrong thread (because
invokerequired=true).

I swear to you, it works :-) If you launch my program you will see
that the loop in DoWork() works fine and yet it has
InvokeRequired=True. Therefore the solution you gave me do work, but
i'm still confused.

I belive that it works. Here it works, too, but in other cases, whenever you
acces a control from the wrong thread, you might get an exception instead.
Believe me, what you're doing is wrong because you must call
Invoke/BeginInvoke. You can feel lucky that you don't get an exception, but
still it's wrong.

More in the answer to your next posting.


Armin
 
I reply to myself....:-)
After:
frmProgressRef.UpdateLabel("30%")
frmProgressRef.UpdateBar(30)
I do:
Application.DoEvents()
that fixes the display bug.
Can you confirm it is the right way to do it?
 
Sam said:
Actually, if you don't mind, I have two more questions. I've updated
my sample application and made it clearer and easier to use, you can
get it here:

http://graphicsxp.free.fr/WindowsApplication11.zip

When I click on 'Load child form', the child form is not shown until
the progression is finished, and I don't understand why?

Because the steps are
1. Load
2. Show

If "Load" isn't done because your loop is running, the Form isn't visible
yet.

Then if you click on 'Click me', you will see the bug I describe on
the form (in a richtextbox) to make it easier to reproduce.


Probably, the content is partially painted immediatelly, but the rest is
painted as soon as the application is idle. It's idle as soon as UpdateUI
has finished.


Armin
 
You are so fast that I get you new message when I'm still about to answer
your previous ones. :-)
I reply to myself....:-)
After:
frmProgressRef.UpdateLabel("30%")
frmProgressRef.UpdateBar(30)
I do:
Application.DoEvents()
that fixes the display bug.
Can you confirm it is the right way to do it?


I can only confirm that it is the wrong way. :-) Not everything that works
is the right way. :-) Reason: DoEvents processes all messages in the message
queue. This can cause reentrance and unpredicted (unwanted) results. Try it
on your own: Click "click me". While the progress is updating, press Alt+F4.
Then click "click me" again. You'll get an InvalidOperationException.


Some general words:
It does not make sense to put the whole work in a method that is called by
Invoke/BeginInvoke. The whole work is then executed in the UI thread that is
blocked again. You should only update the controls in the UI thread. The
loop itself must run in it's own thread. Otherwise it wouldn't make sense
because your new thread is there to prevent the UI thread from being locked.
If you do only update the controls in the UI thread, there is no need for
DoEvents or for calling the Refresh method because the UI thread is not
busy. Therefore it has the time to update the controls without calling the
Refresh method. Only in your example it got necessary to call Refresh
because you blocked the UI thread.


Armin
 
Hi Armin,
If "Load" isn't done because your loop is running, the Form isn't visible
yet.

But the loop runs in a thread, therefore the Load method should
complete before the thread is finished ?

I can only confirm that it is the wrong way. :-) Not everything that works
is the right way. :-)
Yes! I'm starting to realize this :-(

Thank you for your advice. I'm not sure how I can refresh the form AND
the progressForm while at the same time the loop is running.... Would
you mind modifying this portion of code according to whatever method
you think is correct?

Thank you.
 
Sam said:
Hi Armin,


But the loop runs in a thread, therefore the Load method should
complete before the thread is finished ?

In Form1_Load, you call ShowProgressForm which calls
frmProgressRef.ShowDialog. ShowDialog does not return before the Form
(frmProgressRef) is closed. If you would want to continue after showing a
Form, you must call Show instead of ShowDialog. The progress form is closed
after the progress is complete. That's the point when ShowDialog returns to
Form1_Load. After that, the Form is shown.

Yes! I'm starting to realize this :-(

Thank you for your advice. I'm not sure how I can refresh the form
AND the progressForm while at the same time the loop is running....
Would you mind modifying this portion of code according to whatever
method you think is correct?


This is a little bit more complicated because I do not exactly know how your
application is to behave. I've just written a lot about this and made some
suggestions, but now I removed the lines because it turns out that all
depends on the answer to this question: Do you want that the user is able to
continue working with the MDI application while the work is done in the
background? Be aware that this wouldn't make it possible to have a modal
progress form in the forerground. If you could please answer this question
first, it's a litte bit simpler for me to answer and I won't have to write
that much.

Armin
 
Armin,
To answer the question:
No, the user should not be able to interact with ANYTHING at all during
the progression.
Expected behaviour sequence is:

1.child form is loaded
2.in load of child form, the thread is created and the progress from is
shown (it should be modal)
3.during progression, frmProgress is updated (progressbar moved and %
increases) AND child form is also
updated (select correct tab, fill datagrid...)
4. frmProgress is closed when progression is finished, and then the
user is able to interact with child form.

Hope it makes it clearer, sorry if it was a little bit confusing.

Thank you again
 
Sam said:
Armin,
To answer the question:
No, the user should not be able to interact with ANYTHING at all
during the progression.
Expected behaviour sequence is:

1.child form is loaded
2.in load of child form, the thread is created and the progress from
is shown (it should be modal)
3.during progression, frmProgress is updated (progressbar moved and
% increases) AND child form is also
updated (select correct tab, fill datagrid...)
4. frmProgress is closed when progression is finished, and then the
user is able to interact with child form.


Ok. As you want the progress form to be modal, the child /must/ be visible
before the progress form is shown modally. If the child form was not
visible, it would be shown after the progress form is closed, i.e. after
ShowDialog returns - as it is now.


Long story short, I uploaded an example to show the way I would do it. Only
one possible approach, of course:
http://people.freenet.de/armin.zingler/ThreadWithProgress.zip

What I do is:
1. Show child
2. Show progress Form modally
3. Start thread

It's possible to exchange steps 2 and 3, but then you might run into the
problem that the thread tries to update the progress Form before it is
shown. This could be handled, too, but the order above is the simpler one.

If you've got questions to the example, please let me know. (It's only one
of many out there)

In Framework 2.0, they also introduced the
System.ComponentModel.BackgroundWorker class that can help you implement
this pattern. (I haven't used it yet).


Armin
 
In Framework 2.0, they also introduced the
System.ComponentModel.BackgroundWorker class that can help you implement
this pattern. (I haven't used it yet).

I still want to use VS2003 so I don't want to use this control.

Yes, your sample looks quite like what I want to achieve. The only
issue I can see, is that I don't want a Worker class. In fact in my
final application, there will be many child forms (only one opened at a
time) each with a different DoWork function. Therefore the thread
should be started from the child form. This is why I keep a global
reference to my progressForm, so that it can be accessed from any
child.
Is it a problem?

Thanks
 
Sam said:
I still want to use VS2003 so I don't want to use this control.

Yes, your sample looks quite like what I want to achieve. The only
issue I can see, is that I don't want a Worker class. In fact in my
final application, there will be many child forms (only one opened
at a time) each with a different DoWork function. Therefore the
thread should be started from the child form. This is why I keep a
global reference to my progressForm, so that it can be accessed from
any child.
Is it a problem?


If DoWork always has to be started right after showing the child, you can
start the thread in the Activated event of the child Form within the child
(or in OnActivated). You can also use a global reference to the progress
Form.


Armin
 
Ok, but in fact, I can't see the difference between what i've been
doing since the beginning of this post, and the way you are doing it in
your sample.
as you said you :
1. Show child
2. Show progress Form modally
3. Start thread

which is what I'm doing too in my sample. The only difference is the
Worker class, as in my sample I put every loading in the DoWork
function.

And yet, I get this painting issue....
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top