please help on multithreading

S

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?
 
A

Armin Zingler

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

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
 
A

Armin Zingler

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

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
 
A

Armin Zingler

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

Branco Medeiros

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... ;-)
 
B

Branco Medeiros

Sam wrote, in reply to Armin:
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.
 
S

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,
 
B

Branco Medeiros

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

Sam,

First, sorry for the confusion. My last two posts suggest two sepparate
solutions, but it didn't become clear that the last post was
introducing a new approach, not complementing the previous one.

I noticed the confusion by checking your implementation, where both
solutions are attempted... :-D

Summarizing:

Solution 1, with multithreading:
You implement a base AsyncWork class which must be inherited by the
actual workers. The Progress form launches the work with a call to the
worker's Work method, just before going modal. This approach is more
complicated because of the issues related to multithreading (such as
the UI updating) and the extra classes, etc, but would give you a solid
framework that would Just Work (tm) as soon as you resolved these basic
issues. I'd use this solution if there's not much UI updating while
executing the concurrent work, or this UI updating can be modularized
in some way.

Solution 2, without multithreading
Every child form has a DoWork method (just as you were implementing).
As in the previous solution, you rely in the Progress form to start the
work, but since this doesn't use multithreading, you must take
additional steps such as using the progress form's Activate event (see
the code bellow). Likewise, the child form's DoWork method must rely in
Application.DoEvents to keep the UI updated. This approach seems more
ad hoc, but I'd dare to use it if most of the work consisted of UI
updates that could not be dettached from the actual background work,
which seems to be your case.

In your sample both solutions are half way implemented, I guess that's
why you had problems...

To implement Solution 2, which seems to adapt more seemlessly to your
original approach, this is what your classes would look like (notice
that I changed the order of the tests in the progress form Activate
event -- :p ):

'In the progress form
Public Shared ReadOnly DefaultInstance As New frmProgress

Public Delegate Sub DoWorkDelegate()

Private mDone As Boolean
Private mWorker As DoWorkDelegate

Public Sub Progress_Activate( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Me.Activated
If Not mWorker Is Nothing Then
If Not mDone Then
mDone = True
Application.DoEvents()
mWorker()
Close()
End If
End If
End Sub

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

Public Sub UpdateProgress( _
ByVal Text As String, _
ByVal Percent As Integer)
ProgressBar1.Value = Percent
Label1.Text = Text
Label1.Refresh()
End Sub

Public Sub InitProgress( _
ByVal Text As String, _
ByVal Percent As Integer)
ProgressBar1.Minimum = 0
ProgressBar1.Value = 0
ProgressBar1.Maximum = Percent

Label1.Text = Text
Label1.Refresh()
End Sub


Also, replace the child's form code with this sample:

'In Child Form
Sub DoWork()
frmProgress.DefaultInstance.InitProgress("", 100)
For i As Integer = 0 To 100
frmProgress.DefaultInstance.UpdateProgress(i.ToString & "%", i)
Label1.Text = i.ToString
Application.DoEvents()
Next
Dim dt As String() = {"test", "test1", "test2"}
DataGrid2.DataSource = dt
TabControl1.SelectedTab = TabPage2
Application.DoEvents()
End Sub

Private Sub Form1_Load( _
ByVal sender As Object, _
ByVal e As System.EventArgs _
) Handles MyBase.Load
Show()
Dim Work As frmProgress.DoWorkDelegate
Work = AddressOf DoWork
frmProgress.DefaultInstance.DoWork(Work)
End Sub

Private Sub Button1_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs _
) Handles Button1.Click
Dim Work As frmProgress.DoWorkDelegate
Work = AddressOf DoWork
frmProgress.DefaultInstance.DoWork(Work)
End Sub

Finally, disable the TopMost property of the progress form (I don't
know why, but it was preventing the form from showing).

HTH.

Regards,

Branco.
 
S

Sam

Branco,
Thank you for this reply, it makes sense now :)
To give you a feedback on the results:

1. It works just fine with my sample. The UI is updated correctely, the
progression is shown properly in the progressForm. Also the user cannot
click anywhere on the childform/MDI parent until the progression is
complete, which is what I want.

2. In my real application (the big one :)), there is an issue as
during the progression, I can click on my MDI parent, which reacts to
the click ! For example, I open my child forms from a Treeview. While
one form is loading and the progression is shown in the progressForm, I
can click on another node of my tree, which leads to issues. I don't
understand how this is possible, as the progressform is shown modally !

Also, I've put Application.DoEvents() into UpdateProgress ( ), but I
guess this is no big deal, is it?

Do you have any idea on why this is happening ?

Thank you so much again
Regards,
 
S

Sam

I've found out what's wrong. VS2003 is the problem. I've reproduced the
same behaviour with the sample under VS2003. During the progression if
I click on the childform, it is responsive, whereas under VS2005 it is
not.
This is something that I can't explain... I guess this is not directely
related to this post and I should do another post on this forum
regarding this issue. Unless one of you has the answer ;-) ?
 
S

Sam

Wooooo I'm so close to get it exactely as I wanted at the beginning :-(
I'm just stuck with this one last issue regarding the Form not being
modal under VS2003...
Can anyone help please ?

Regards;
 
S

Sam

Right, just to say that I've moved to Visual studio 2005 (had to do it
anyway) so my issue is solved. Huge thanks to you guys for helping me
with this, it works great now.
 

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

Top