471,073 Members | 1,134 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,073 software developers and data experts.

please help on multithreading

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:)

Nov 23 '05 #1
55 3038
Hi,

http://msdn.microsoft.com/msdnmag/is...s/default.aspx

Ken
------------------
"Sam" <sa**************@voila.fr> wrote in message
news:11**********************@o13g2000cwo.googlegr oups.com...
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:)

Nov 23 '05 #2
Sam
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

Nov 23 '05 #3
Sam
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

Nov 23 '05 #4
"Sam" <sa**************@voila.fr> schrieb
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

Nov 23 '05 #5
Sam
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

Nov 23 '05 #6
"Sam" <re************@hotmail.co.uk> schrieb
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/en...sagequeues.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

Nov 23 '05 #7
Sam
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.

Nov 23 '05 #8
Sam
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

Nov 23 '05 #9
Sam
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?

Nov 23 '05 #10
"Sam" <re************@hotmail.co.uk> schrieb
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).

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.


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

Nov 23 '05 #11
"Sam" <re************@hotmail.co.uk> schrieb
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

Nov 23 '05 #12
"Sam" <re************@hotmail.co.uk> schrieb

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

Nov 23 '05 #13
Sam
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.

Nov 23 '05 #14
"Sam" <re************@hotmail.co.uk> schrieb
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 ?


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.

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?

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
Nov 23 '05 #15
Sam
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

Nov 23 '05 #16
"Sam" <re************@hotmail.co.uk> schrieb
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.zingl...thProgress.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

Nov 23 '05 #17
Sam
>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

Nov 23 '05 #18
"Sam" <re************@hotmail.co.uk> schrieb
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?

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

Nov 23 '05 #19
Sam
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....

Nov 23 '05 #20
"Armin Zingler" <az*******@freenet.de> schrieb

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.


I'm about to upload a new example how to do this, but I currently have
problems with my provider. So, might take some hours....
Armin

Nov 23 '05 #21
"Sam" <re************@hotmail.co.uk> schrieb
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.
One difference is that I show the progress Form *after* the child has been
shown. You show the progress Form *before*.

The other difference is that your loop is running in the UI thread, whereas
my loop is running in the new thread.
And yet, I get this painting issue....


What's your current project? If you upload it again, I'd appreciate the VB
2003 version (too). :-)

Armin

Nov 23 '05 #22
Sam wrote:
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! <snip>

As Armin has been telling you, there are two big issues with the code
you presented:

a) Your UpdateUI method on Form1 is executing in the UI thread, not in
a separate thread as you'd think. and

b) Form1 won't show while the progress form is visible because the
progress form is modal, and was shown before Form1 had a chance to show
itself.

One way to resolve the second issue, is to ensure that Form1 is
actually visible before showing the progress form modally:

Private Sub Form1_Load(...) Handles MyBase.Load
Show() '<-- shows itelf
StartWork() '<-- More on this in a minute
frmmainref.ShowProgressForm() '<-- Shows the progress form modally
End Sub

You could also show the progress form when Form1 activates for the
first time, as Armin suggested.

Now for the proverbial work, I mean, for your
StartWork->DoWork->UpdateUI job.

You're having issues with the UI while running the thread because
you're executing UpdateUI *in the UI thread*, not in a separate thread.

Yes, StartWork does launch DoWork in another thread:

Public Sub StartWork()
'...
myThread = New Thread(AddressOf DoWork)
myThread.Name = "Thread1"
myThread.Start()
End Sub

But then DoWork, even running in another thread, calls UpdateUI in a
way that switches back to the UI thread:

Public Sub DoWork()
Invoke(New UpdateUIDelegate(AddressOf UpdateUI))
BeginInvoke(frmProgressRef._close)
End Sub

The Invoke method of Form1 says: "Execute the following delegate on the
UI thread and just return after the delegate finishes". I'm sure it's
not this that you meant (Notice that delegates have an Invoke method
also, but its semantic has nothing to do with the Form's Invoke
method).

The BeginInvoke method of Form1 is almost the same: "Dispatch the
following delegate to be executed by the UI thread and return
immediatly".

Now, if the UpdateUI method is happening in the UI thread, no wonder
you'll found painting issues: it's as if you hadn't used a sepparate
thread at all, in the first place. DoWork's secondary thread is being
used just to bypass the ShowDialog call to the progress form, in
practice allowing both the progress form to show modaly and your code
to run in the same thread.

What I suggest is for you to completely decouple the work being done
from the update of the UI, which would mean a completely different
approach.

To fix the code as it is, just to show you were things are going wrong,
I'd do the following:
From Form1, delete the declarations of DoWork, UpdateUI and the

UpdateUIDelegate. Copy/Paste the following code:

'In Form1
Private Delegate Sub UpdateLabelDelegate(ByVal Text As String)
Private Delegate Sub UpdateTableDelegate(DataSource As Object)

Private Sub DoWork
For i As Integer = 0 To 100
Dim Value As String = i.ToString
frmProgressRef.AsyncUpdateProgress(Value & "%", i)
AsyncUpdateLabel(Value)
AsyncOpDone.WaitOne(10, True)
Next
frmProgressRef.AsyncUpdateProgress("30%", 30)
AsyncOpDone.WaitOne(1000, True)

Dim dt As String() = {"test", "test1", "test2"}
AsyncUpdateTable(dt)

frmProgressRef.AsyncUpdateProgress("100%", 100)
AsyncOpDone.WaitOne(1000, True)
frmProgressRef.AsyncClose
End Sub

Private Sub AsyncUpdateLabel(ByVal Text As String)
If InvokeRequired
Static T As New UpdateLabelDelegate(AddressOf AsyncUpdateLabel)
Dim Params() As Object = {Text}
BeginInvoke(T, Params)
Else
Label1.Text = Text
End If
End Sub

Private Sub AsyncUpdateTable(ByVal DS As Object)
If InvokeRequired
Static T As New UpdateTableDelegate(AddressOf AsyncUpdateTable)
Dim Params() As Object = {DS}
BeginInvoke(T, Params)
Else
DataGrid2.DataSource = DS
TabControl1.SelectedTab = TabPage2
End If
End Sub

The code that were formerly in UpdateUI is now in DoWork. Notice that
now I'm calling some AsyncXXXX methods to update the UI, which didn't
exist before.

In frmProgress, delete the declarations of UpdateBar and UpdateLabel,
and Copy/Paste the snippets bellow:

'In frmProgress
Private Delegate Sub UpdateProgressDelegate( _
ByVal Text As String, ByVal Percent As Integer)

Private Delegate Sub CloseFormDelegate()

Public Sub AsyncUpdateProgress( _
ByVal Text As String, ByVal Percent As Integer)
If InvokeRequired
Static T As New UpdateProgressDelegate( _
AddressOf AsyncUpdateProgress)

Dim Params() As Object = {Text, Percent}
BeginInvoke(T, Params)
Else
ProgressBar1.Value = Percent
Label1.Text = Text
End If
End Sub

Public Sub AsyncClose
If InvokeRequired
Static T As New CloseFormDelegate(AddressOf AsyncClose)
BeginInvoke(T)
Else
Close
End If
End Sub
Now, If run the application, it'll probably work the way you
intended...

HTH,

Regards,

Branco.

Nov 23 '05 #23
"Armin Zingler" <az*******@freenet.de> schrieb
I'm about to upload a new example how to do this, but I currently have
problems with my provider. So, might take some hours....


Still problems with my provider (power outage)...
Armin
Nov 23 '05 #24
Sam
Branco,
Now, If run the application, it'll probably work the way you
intended...

Indeed ! It is too good to believe :-)
Your approach is wonderful and works as I want. May I just bring a
problem ? It's just that my 'real' application is much bigger than this
sample, and updating the UI is much more than just filling one grid, it
is filling many grids, selecting rows, checking for errors in the
grid.... (yes, all that at form loading :-)) Therefore do you think I
should have one AsyncXXX function per operation or a single Async
function for doing everything?
Thank you again, that really helps!

Armin,
I'm interested in your new sample, maybe I can get the best of both
world? Let me know when your provider is back in business!
Thank you.

Nov 23 '05 #25
"Sam" <re************@hotmail.co.uk> schrieb
Branco,
Now, If run the application, it'll probably work the way you
intended... Indeed ! It is too good to believe :-)
Your approach is wonderful and works as I want. May I just bring a
problem ? It's just that my 'real' application is much bigger than
this sample, and updating the UI is much more than just filling one
grid, it is filling many grids, selecting rows, checking for errors
in the grid.... (yes, all that at form loading :-)) Therefore do you
think I should have one AsyncXXX function per operation or a single
Async function for doing everything?
Thank you again, that really helps!


I'm not Branco, but I may add: In general, if your background thread is
mainly doing a lot of UI stuff, it doesn't make much sense to put it in a
background thread, because a) the purpose of a background thread is to keep
the UI as responsible as possible. This doesn't happen any more of you send
all the work back to the UI thread b) It slows things down at run time due
to thread marshalling overhead c) It slows things down at design time
because there's more to code.
Armin,
I'm interested in your new sample, maybe I can get the best of both
world? Let me know when your provider is back in business!
Thank you.


Haven't looked yet today but I'm about to... :)
Armin

Nov 23 '05 #26
Sam
Actually, just to give you an idea of what i do in my DoWork function
of my Application (not the sample):

'get general datatables (access to the business object)
m_dsFieldColumns = m_fields.GetFieldsColumns

'fill one grid
FillDetailGrid()

'access to other private member created on the UI thread
m_detailCount = m_dsDetail.Tables(0).Rows.Count
m_dsFlow = m_fields.GetFlowCtrl

'select a row in the grid and inside this function I also fill
other grids
'and I access the business object again.
SelectControlInGrid(m_field.FieldId)

So as you can see, there are many calls to things created on the UI
thread, and within those functions, there are nested calls to other
UI-created stuff. Therefore it gets very tricky to do !
Besides I still need to be able to update my progressForm in between
the above calls !

Branco, I think your method is great and works fine for what my sample
was meant to do, but could you put me on track for an approach that
would suit my requirements for my application? Again, it is very
similar to the sample, except that it is more complex du to the number
of grids, operations, ...

Thank you.

Nov 23 '05 #27
Sam
Armin,
I think you are right on this point:
if your background thread is mainly doing a lot of UI stuff, it >doesn't make much sense to put it in a background thread


It is exactely the problem I'm running in.
On the other hand, my forms are slow to display du to the amount of
database operations being done when the forms are loading. Therefore,
the display is ugly during a few seconds. Hence my idea to come up with
a progressForm to keep the user waiting, and to have the display of the
form being done 'smoothly'. Do you think it is a bad approach ? Do you
think I should keep the ugly display when loading my forms and forget
about multi-threading ?

Thank you

Nov 23 '05 #28
"Sam" <re************@hotmail.co.uk> schrieb
Armin,
I think you are right on this point:
if your background thread is mainly doing a lot of UI stuff, it
doesn't make much sense to put it in a background thread

It is exactely the problem I'm running in.
On the other hand, my forms are slow to display du to the amount of
database operations being done when the forms are loading.


Hehe, maybe it's slow *because* you are doing things in the *background*?
;-)
Therefore,
the display is ugly during a few seconds. Hence my idea to come up with
a progressForm to keep the user waiting, and to have the display of the
form being done 'smoothly'. Do you think it is a bad approach ? Do you
think I should keep the ugly display when loading my forms and forget
about multi-threading ?


I think, it's better to immediatelly show the form without anything going on
in the background. That appears the quickest to the user. After that, you
will have a progress bar anyway. As a lot of code spends it's time to update
the UI, I probably would put it all in the UI thread.

Written this, I must admit that updating the display is an issue. The
Refresh method should help, if everything is done in the UI thread. Again,
be aware of the I-force-the-update-but-WinXP-doesn't-let-me issue.

Maybe in this case it is an option to use Application.Doevents. Usually, I'm
not a friend of it, but as a progress form is shown modally, not much can
happen while executing DoEvents. You must prevent the progress Form from
being closed by the user (Alt+F4 ist AFAIK still possible), but that's
nearly all and could be solved easily. So, thinking about it, no background
thread + DoEvents is my favorite in this case.
Armin

Nov 23 '05 #29
Sam
>Hehe, maybe it's slow *because* you are doing things in the *background*?

Well, yes it is slow because of the database operations, but not
because it is in a thread. If I put all the code back in the Load event
of my form, it is slow as well, but on top of that the form doesn't
show properly until the work is complete.
From my understanding, the way you would do it is :


public sub FormLoad ( ...) handles mybase.Load
Show()
'do all the heavy work
end sub

In this case :
1. The form is not properly displayed until work is complete, despite
the explicit Show() call.
2. When do you show the progressForm dialog, without stoping the
execution of the work ?

public sub FormLoad ( ...) handles mybase.Load
Show()
'do all the heavy work
frmMainRef.ShowProgressForm() -> the work has already been done so no
need to show this...
end sub

public sub FormLoad ( ...) handles mybase.Load
frmMainRef.ShowProgressForm() -> the code stops here and the rest is
not processed
Show()
'do all the heavy work
end sub

Thank you.

Nov 23 '05 #30
Sam
Armin,
Ok if I call
Show()
Application.DoEvents
Then the form is displayed properly. But I remember yesterday you told
me not to do that ?
Besides, that doesn't solve my progressForm issue. What should I do
with it ?

Thank you

Nov 23 '05 #31
"Armin Zingler" <az*******@freenet.de> schrieb
"Armin Zingler" <az*******@freenet.de> schrieb
I'm about to upload a new example how to do this, but I currently
have problems with my provider. So, might take some hours....


Still problems with my provider (power outage)...

Now it's there: (meanwhile a little bit old ;-)
http://people.freenet.de/armin.zingl...thProgress.zip

Armin
Nov 23 '05 #32
"Sam" <re************@hotmail.co.uk> schrieb
Hehe, maybe it's slow *because* you are doing things in the
*background*?
Well, yes it is slow because of the database operations, but not
because it is in a thread.


I meant, loading the form is slow because your doing these operations in the
background, instead of first showing the form, then starting the operations.
Please don't take this sentence too serious. (sometimes I really have
problems to find the right words because English is not my native language).
If I put all the code back in the Load
event of my form, it is slow as well, but on top of that the form
doesn't show properly until the work is complete.

From my understanding, the way you would do it is :


No, I never call Show within load. See my new example.
public sub FormLoad ( ...) handles mybase.Load
Show()
'do all the heavy work
end sub

Armin

Nov 23 '05 #33
Sam
Armin,
Please don't take this sentence too serious. (sometimes I really have
problems to find the right words because English is not my native language).

No worries;) it's not mine either anyway...

Is it me or your sample is exactely the same as the previous version ?
Are you sure you gave me the link to the 'new' sample ?

Nov 23 '05 #34
Sam
oh no please ignore my last post;;; my mistake !
sorry. I'm going to look at it now

Nov 23 '05 #35
Sam
Ok, I've looked at your sample. The way you show the progress form is
quite not like what I'm used to but it looks good.
However we said we were going to go to another approach, i.e. without a
background thread.
So to summarize, the new approach is:

public sub form_Load( ... ) handles myBase.Load
Show( )
Application.DoEvents( ) 'forces form to show properly before starting
work
DoWork( ) 'this is not done in a separate thread anymore, it is done in
the UI thread.
end sub

Then the new issue is, how to provide the user with a feedback on
progression? How can I show and update the progressForm on this basis ?
I'm trying to think about a way to do it but I'm quite idea-less :-)

Can you help?
Thank you

Nov 23 '05 #36
"Sam" <re************@hotmail.co.uk> schrieb
Ok, I've looked at your sample. The way you show the progress form
is quite not like what I'm used to but it looks good.
However we said we were going to go to another approach, i.e.
without a background thread.
So to summarize, the new approach is:
No, this is not the new approach. I never use Show in Form_Load. It doesn't
make sense because Form_Load is caused by calling Show before.

In addition, I don't use DoEvents in Form_Load. I'd use it *within* DoWork.
You need it to give the UI the time to update the screen.
public sub form_Load( ... ) handles myBase.Load
Show( )
Application.DoEvents( ) 'forces form to show properly before
starting work
DoWork( ) 'this is not done in a separate thread anymore, it is done
in the UI thread.
end sub

Then the new issue is, how to provide the user with a feedback on
progression? How can I show and update the progressForm on this
basis ? I'm trying to think about a way to do it but I'm quite
idea-less :-)


I you use DoEvents within DoWork, this problem is solved too, because
DoEvents
also gives the progress Form the time to update itself.

I'm gonna make another example using DoEvents.... :)

Armin

Nov 23 '05 #37
"Armin Zingler" <az*******@freenet.de> schrieb

I'm gonna make another example using DoEvents.... :)


Done.

http://people.freenet.de/armin.zingl...ndDoevents.zip

Armin
Nov 23 '05 #38
Sam
>No, this is not the new approach. I never use Show in Form_Load.
I know, Branco did.

But as you said earlier, having the UI code in DoWork is going to be a
pain to code, because I've got so many things to update that it would
get really complex if I had to use delegates.

So in fact, we want to leave the UI code in the load event of the form.
However a thread should probably be required for updating the
progressForm, is that right ?

I'm curious to see your new sample...
Thank you.

Nov 23 '05 #39
"Sam" <re************@hotmail.co.uk> schrieb
No, this is not the new approach. I never use Show in Form_Load. I know, Branco did.


Oh, I see.
But as you said earlier, having the UI code in DoWork is going to be
a pain to code, because I've got so many things to update that it
would get really complex if I had to use delegates.

So in fact, we want to leave the UI code in the load event of the
form. However a thread should probably be required for updating the
progressForm, is that right ?
Well, I thought we are currently discussing how to do it without a thread.
I'm curious to see your new sample...
Thank you.

Addition to the latest sample:
In an earlier posting I wrote, pressing Alt+F4 while the progress Form is
shown might be a problem, but it seems that is is not. But: What you have
to consider is that the application can loose and regain the focus. This
leads to a second Activated event. As we want to start the work only with
the first event, you have to put a check in it. Something like:

Private f_First As Boolean

Public Shadows Sub Show()
'...
f_First = True
ProgressForm.DefaultInstance.ShowDialog()
'...
End Sub

Private Sub OnProgressFormActivated( _
ByVal sender As Object, ByVal e As System.EventArgs)

If Not f_First Then Return

f_First = False
'...
End Sub
Armin

Nov 23 '05 #40
Sam
Thanks. I will do that.
But at the moment I'm trying to understand why my code doesn't even go
into the 'Shadowed' Show function ! My child form is inherited from a
BaseForm, would that be the problem?

Nov 23 '05 #41
"Sam" <re************@hotmail.co.uk> schrieb
Thanks. I will do that.
But at the moment I'm trying to understand why my code doesn't even
go into the 'Shadowed' Show function ! My child form is inherited
from a BaseForm, would that be the problem?

Yes, if the variable is declared as the BaseForm, it does not work because
Show is not overridable. You must declare it as the type that contains the
new Show method (childform in my case).

Or you can implement it in the base form, like:
Public Sub Run()

MyBase.Show()
AddHandler ProgressForm.DefaultInstance.Activated, AddressOf
OnProgressFormActivated
f_First = True
ProgressForm.DefaultInstance.ShowDialog()
RemoveHandler ProgressForm.DefaultInstance.Activated, AddressOf
OnProgressFormActivated

End Sub

protected mustoverride Sub DoWork() '<==== must be implemented in the
derived forms

Private Sub OnProgressFormActivated(ByVal sender As Object, ByVal e As
System.EventArgs)

If Not f_First Then Return

f_First = True

DoWork

ProgressForm.DefaultInstance.Close()

End Sub

Armin

Nov 23 '05 #42
Sam
I've used the second solution you gave me. Assuming Run() is to be
called in the load event of the base form, it does not look good at
all, takes longer to load and frmProgress does not show up.
Is Run() supposed to be called in the load event of the base form ?
Here is my code in DoWork overriden in the child form:

Protected Overrides Sub dowork()
m_isCellUpdated = False
m_PrevField = 0
m_IgnoreEvents = True
m_lstTblMgr = New ListTableManager
m_lstRootTblMgr = New ListTableManager

If Not m_queries Is Nothing Then
FillQueryForm()
frmProgress.DefaultInstance.Progress = 30
Threading.Thread.Sleep(1000)
Application.DoEvents()
FillGrids()
frmProgress.DefaultInstance.Progress = 70
Threading.Thread.Sleep(1000)
Application.DoEvents()
End If

frmMainRef.DeleteButton = True
frmMainRef.TokenButton = True
frmMainRef.TBButtonToken.ToolTipText = "Add token fields to
this query"

If m_queries.CurrentRecord.QueryID = 0 Then
MyBase.TBButtonToken.Enabled = False
End If

frmProgress.DefaultInstance.Progress = 100
Threading.Thread.Sleep(1000)
Application.DoEvents()

frmProgress.DefaultInstance.Close()

m_IgnoreEvents = False
End Sub

Nov 23 '05 #43
"Sam" <re************@hotmail.co.uk> schrieb
I've used the second solution you gave me. Assuming Run() is to be
called in the load event of the base form, it does not look good at
all, takes longer to load and frmProgress does not show up.
Is Run() supposed to be called in the load event of the base form ?
Here is my code in DoWork overriden in the child form:

No no, not in load. Usage (in MDIParent):

dim f as baseform

f = new Childform 'Childform is derived from baseform
f.mdiparent = me
f.run 'shows the form and starts to run
Armin
Nov 23 '05 #44
Sam
Sorry.
I got it "working". But i must say this method is very inefficient with
my application, I don't know why. I'm doing exactely like in your
sample (except I use the run () function in my baseform).
I'm not very happy with this way of doing it, no offence at all ! I
really appreciate your efforts !

That said, I would like to explain again the way I would like to do it:
- Call Show( ) and Application.DoEvents in the load event of my child
forms, as Brenco said, to keep the display smooth
- Keep the work in the load event of my child forms
- Find a way to show and update the progressForm (others than the last
method you suggested). Maybe using delegates ?

I'm keen to have this working smoothly, it's quite important to me. I'm
just trying to figure out the best way to do it.

Thank you

Nov 23 '05 #45
"Sam" <re************@hotmail.co.uk> schrieb
Sorry.
I got it "working". But i must say this method is very inefficient
with my application, I don't know why. I'm doing exactely like in
your sample (except I use the run () function in my baseform).
I'm not very happy with this way of doing it, no offence at all ! I
really appreciate your efforts !

That said, I would like to explain again the way I would like to do
it: - Call Show( ) and Application.DoEvents in the load event of my
child forms, as Brenco said, to keep the display smooth
- Keep the work in the load event of my child forms
- Find a way to show and update the progressForm (others than the
last method you suggested). Maybe using delegates ?

I'm keen to have this working smoothly, it's quite important to me.
I'm just trying to figure out the best way to do it.

Thank you


I told you what, IMO, is the best solution. It's up to you which way you
want to go. For me, it still doesn't make sense to call Show and Doevents in
Load. I don't know why you want this. That's all I can say about it at the
moment.

Armin

Nov 23 '05 #46
Hi Sam, Armin.

My opinion is that both approaches for initializing the form data -- in
a sepparate thread or in the Activate event -- have pros and cons.

I usually used the Activate event, but in VB.Net I noticed (at least in
my machine) that sometimes the Form is yet to show completely when the
Activate event fires, requiring a non-wanted call to DoEvents (which I
usually have gripes with).

Because I'm just discovering .Net, I tend to use some idioms that are
still remanescent of VB classic. That is the case of the initialization
done in the Activate event, or the use of Show in the Load event. I
recently discovered that in Net 2.0 I'd better use the Shown event,
which is fired only once, when the Form is first shown... ;-)

Anyway, my more recent approach has been to perform initialization in a
separate thread. Of course, this adds a lot of issues regarding
concurrency and UI updating, but I have a few rules of thumb:

a) Have some AsyncXXX methods at Form level that perform a group of UI
related tasks, so when they're called from outside the UI thread they
return as soon as possible after having done the most work possible;
practical examples of this are methods for updating the progress bar
(AsyncUpdateProgress), reporting connection with the DB
(AsyncShowConnection), and the like.

b) If the volume of data that would be put in the UI (grid data,
listbox data, etc) is somewhat heavy, I gather everything in specific
structures and just update the UI when the worker thread returns
(which, on my setup, raises an event on the UI thread, because I
usually use a BackgroundWorker or something like it).

Notice that I didn't have yet necessity to do extensive UI updates
while performing asynchronous work, so there may be situations where
I'll have to consider the trade offs, but my usual experience has been
that a paralel worker thread does wonders to the responsiveness of the
UI.

That being said, I'd like to make a few suggestions to Sam's approach:

Because you know that you'll have a modal Progress form that will be
shown while background operations occur, it's a good idea to isolate
the asynchronous work in a way that *the Progress form* launches it
while entering modal. One possibility could be a AsyncWork class, whose
Work method is called by a ProgressForm.DoWork method, as bellow:

'In a Progress form
Private WithEvents Worker As AsyncWork

Sub DoWork(Work As AsyncWork)
Worker = Work
If Worker IsNot Nothing then
Worker.Work '<- Starts asynchronally
ShowDialog '<- shows modally
End If
End Sub

Sub Progress_FormClosing(...) Handles Me.FormClosing
If Worker.IsBusy Then
'Asks if the user wants to cancel
E.Cancel = True
Worker.PauseWork
If MsgBox("Stop this work?", _
MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
Worker.Cancel = True
End if
Worker.ContinueWork
End If
End Sub

Sub Worker_Progress(...) Handles Worker.Progress
'Updates the progress
If InvokeRequired Then
'Calls itself with BeginInvoke
Else
'...init, update or otherwise finish the progress
End if
End Sub

Sub Worker_Finished(...) Handles Worker.Finished
'Finishes the work
If InvokeRequired Then
'Calls itself with BeginInvoke
Else
'Running in the UI thread. Close the form
DialogResult = 'SomeValue
Close
End if
End Sub
Likewise, just as you know that you will have some heavy concurrent
work, it pays to isolate this in a class with a specific protocol,
namely: a cancel flag; progress events; a finished event, a Busy flag,
and so on. One approach would be to consider the BackgroundWorker
object from Net 2.0. Since it seems you preffer (or have) to keep your
current pre-Net 2.0 setup, it may be advisable that you cook one
background worker yourself... :-)

Imports SysThread = System.Threading

Class MustInheirt AsyncWork

Private mCancel As Boolean
Private mBusy As Boolean
Private mLock As New Object
Private mResult As Boolean

Public Event Progress(...)
Public Event Finished(...)

Protected MustOverride Function DoWork As Boolean

Public ReadOnly Property IsBusy As Boolean
Get: Return mBusy: End Get
End Property

Public ReadOnly Property Result as Boolean
Get: Return mResult: End Get
End Property

Public Property Cancel As Boolean
Get
Return mCancel
End Get
Set(ByVal Value As Boolean)
mCancel = Value
End Set
End Property

Public Sub PauseWork
SysThread.Monitor.Enter(mLock)
End Sub

Public Sub ContinueWork
SysThread.Monitor.Exit(mLock)
End Sub

Protected Function CancelWork As Boolean
'Must be called periodically from descendants
PauseWork
ContinueWork
Return mCancel
End Sub

Public Sub Work
Dim T As New SysThread.Thread(AddressOf LaunchWork)
mBusy = True
mCancel = False
mResult = False
T.Start
End Sub

Private Sub LaunchWork
Try
mResult = DoWork
Finally
mBusy = False
RaiseEvent Finished(...)
End Try
End Sub

Protected Sub OnProgress(...)
'Called by descendants to report progress
RaiseEvent Progress(...)
End Sub
End Class

Then, inside your form, you might have:

Private Class InitWork
Inherits AsyncWork
Protected Overrides Function DoWork As Boolean
Dim Result As Boolean
OnProgress(...) 'Initializes the progress
For X = 1 to AVeryLargeValue
If CancelWork Then
Exit For
End if
'Do some work
'...
OnProgress(...) 'show some progress
Next
OnProgress(...) 'Finish the progress
Return Result
End Function
End Class

Private Sub Form1_Load(...) Handles Me.Load
Show() ':-D
Dim Progress As New ProgressForm
ProgressForm.DoWork(New InitWork)
End Sub

HTH.

Regards,

Branco.

P.S: As allways, this is just AirCode. Your results may vary... ;-)

Nov 23 '05 #47
Sam wrote, in reply to Armin:
<snip>
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?

<snip>

Oops...

I just suggested using a Worker class... :-)

Nevertheless, if you really want to keep the code in the children's
DoWork method, then you might add to your Progress form:

Public Delegate Sub DoWorkDelegate()

Private mDone As Boolean
Private mWorker AS DoWorkDelegate

Public Sub Progress_Activate(...) Handles Me.Activate
If not mDone Then
mDone = True
If Not mWorker Is Nothing Then
Application.DoEvents
mWorker()
End If
Close()
End If
End Sub

Public Sub DoWork(Worker As DoWorkDelegate)
mDone =False
mWorker = Worker
ShowModal
End Sub

Then in the child form's DoWork method, you could perform your update,
using DoEvents to allow the display to refresh if necessary, with no
need of a background thread, as Armin suggests:

'In your child form
Sub DoWork()
For X = 1 To VeryBigValue
'Dothis
'DoThat
GlobalProgressDlg.UpdateProgress(X)
Next
End Sub

Form1_Load(...) Handles Me.Load
Show()
Dim Work As ProgressDlg.DoWorkDelegate
Work = AddressOf DoWork
GlobalProgressDlg.DoWork(Work)
End Sub

HTH.

Regards,

Branco.

Nov 24 '05 #48
Sam
Branco,
First, thank you so much for taking the time to help me, I really
appreaciate that.
I've read your post several times, it took me some time to figure out
what's going on!
I've tried to implement your approach in my sample and I've come across
a number of issues:
The first one is that in Progress_FormClosing, the test :
If Worker.IsBusy Then
fails as Worker is always NULL at that point, and I don't know why.

The second issue I have is that the progressform doesn't show at all.

I've uploaded my sample, maybe you can have a quick look at it. It is
rather close to the code you provided me with, so it should be easy for
you to understand it. I'd be very grateful if you could tell me where
I'm doing wrong in trying to use your code.
http://graphicsxp.free.fr/WindowsApplication11.zip

With regards,

Nov 24 '05 #49
Sam
Armin,
What does IMO stand for ?:-)

Nov 24 '05 #50

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

16 posts views Thread by Robert Zurer | last post: by
6 posts views Thread by Michael C | last post: by
9 posts views Thread by Popoxinhxan | last post: by
1 post views Thread by James | last post: by
7 posts views Thread by Ray | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.