please help on multithreading

  • Thread starter Thread starter Sam
  • Start date Start date
Armin Zingler said:
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 said:
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 said:
Hi,
I have a serious issue using multithreading. A sample application
showing my issue can be downloaded here:
http://graphicsxp.free.fr/WindowsApplication11.zip

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

In the sample I've just done a loop to increase the progressbar, but
obviously in reality the progressbar updates and the operations on
controls (tabcontro, datagrid..) should be nested, hence the issue!
<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 said:
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 said:
Branco,

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 said:
Armin,
I think you are right on this point:

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
 
Sam said:
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.

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 ?
 
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 said:
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 said:
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
 

Ask a Question

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

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

Ask a Question

Back
Top