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:) 55 3194
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:)
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" <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
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" <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
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
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" <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
"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
"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
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" <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
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" <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
>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" <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
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....
"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
"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
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.
"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
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.
"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
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.
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
"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, itdoesn'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
>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.
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
"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
"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
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 ?
oh no please ignore my last post;;; my mistake !
sorry. I'm going to look at it now
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
"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
>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.
"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
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?
"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
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
"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
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
"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
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... ;-)
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.
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,
Armin,
What does IMO stand for ?:-) This thread has been closed and replies have been disabled. Please start a new discussion. Similar topics
by: Robert Zurer |
last post by:
Can anyone suggest the best book or part of a book on this subject. I'm
looking for an in-depth treatment with examples in C#
TIA
Robert Zurer
robert@zurer.com
|
by: Michael C |
last post by:
Hello
Can someone please tell me what I'm doing wrong? I'm writing an application
that should be using callbacks to perform asynchronous calls to the Win32
API. Problem is it never reaches my...
|
by: Popoxinhxan |
last post by:
Dear experts, i want to develop an client application that consume the google
search web service.
In my MainForm i have a method to retrieve all the search result e.g.
GetGoogleResults().
Now i...
|
by: shonend |
last post by:
**** sorry about the length of the message. If you can't read the
whole thing and still willing to help, read the last 2 paragraphs where
the main problem is described. The introduction story is...
|
by: Boni |
last post by:
Dear all,
picturebox mouse move seems to start new thread each time it is called.
At least I see in debuger many threads.
---Non user code--
Picturebox.MouseMove
This seems to spoil my...
|
by: MeowCow |
last post by:
I will try and make my question with out being too long winded. I have
been doing a lot of reading on how to do multithreading and I have
implemented the code from the following example on...
|
by: mrkbrndck |
last post by:
Please see the code below as I am trying to use multithreading for copying
files to a new location in a way that improves performance of the client
windows application.
The problem occurs when 2...
|
by: James |
last post by:
Hi All,
Just going into the world of Multithreading and have a few questions about
using class's in them.
If I create a calls in the main thread then start a new thread that calls
another...
|
by: Ray |
last post by:
Hello,
Greetings! I'm looking for a solid C++ multithreading book. Can you
recommend one? I don't think I've seen a multithreading C++ book that
everybody thinks is good (like Effective C++ or...
|
by: linyimin |
last post by:
Spring Startup Analyzer generates an interactive Spring application startup report that lets you understand what contributes to the application startup time and helps to optimize it. Support for...
|
by: erikbower65 |
last post by:
Here's a concise step-by-step guide for manually installing IntelliJ IDEA:
1. Download: Visit the official JetBrains website and download the IntelliJ IDEA Community or Ultimate edition based on...
|
by: isladogs |
last post by:
The next Access Europe meeting will be on Wednesday 6 Sept 2023 starting at 18:00 UK time (6PM UTC+1) and finishing at about 19:15 (7.15PM)
The start time is equivalent to 19:00 (7PM) in Central...
|
by: DJRhino1175 |
last post by:
When I run this code I get an error, its Run-time error# 424 Object required...This is my first attempt at doing something like this. I test the entire code and it worked until I added this -
If...
|
by: Rina0 |
last post by:
I am looking for a Python code to find the longest common subsequence of two strings. I found this blog post that describes the length of longest common subsequence problem and provides a solution in...
|
by: DJRhino |
last post by:
Private Sub CboDrawingID_BeforeUpdate(Cancel As Integer)
If = 310029923 Or 310030138 Or 310030152 Or 310030346 Or 310030348 Or _
310030356 Or 310030359 Or 310030362 Or...
|
by: lllomh |
last post by:
Define the method first
this.state = {
buttonBackgroundColor: 'green',
isBlinking: false, // A new status is added to identify whether the button is blinking or not
}
autoStart=()=>{
|
by: lllomh |
last post by:
How does React native implement an English player?
|
by: DJRhino |
last post by:
Was curious if anyone else was having this same issue or not....
I was just Up/Down graded to windows 11 and now my access combo boxes are not acting right. With win 10 I could start typing...
| |