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.