BeginInvoke - What I thought I knew
On my dual core system I thought the UI would get updated sometimes when executing this code:
Code:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim t As New Threading.Thread(AddressOf foo)
t.IsBackground = True
t.Start()
End Sub
Private Sub foo()
For x As Integer = 1 To 50000
updUI(x)
'Threading.Thread.Sleep(0)
Next
End Sub
'Dim updUIare As New Threading.AutoResetEvent(True)
Delegate Sub updUIdel(i As Integer)
Private Sub updUI(i As Integer)
If Me.InvokeRequired Then
'updUIare.WaitOne()
Dim d As New updUIdel(AddressOf updUI)
Me.BeginInvoke(d, i)
Else
Label1.Text = i.ToString("n0")
ProgressBar1.Value = i Mod 100
'updUIare.Set()
End If
End Sub
but it does not until the end. I didn't expect it to be pretty. Using the AutoResetEvent works, as would Invoke.
This is a simple reproduction of some code I was testing for a worst case situation.
Re: BeginInvoke - What I thought I knew
The Label won't update because there are no paint messages being sent to it. If the code was this:
Code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim t As New Threading.Thread(AddressOf foo)
t.IsBackground = True
t.Start()
End Sub
Private Sub foo()
For x As Integer = 1 To 50000
updUI(x)
'Threading.Thread.Sleep(0)
Next
End Sub
'Dim updUIare As New Threading.AutoResetEvent(True)
Delegate Sub updUIdel(ByVal i As Integer)
Private Sub updUI(ByVal i As Integer)
If Me.InvokeRequired Then
'updUIare.WaitOne()
Dim d As New updUIdel(AddressOf updUI)
Me.BeginInvoke(d, i)
Else
Me.Label1.Text = i.ToString("n0")
Me.ProgressBar1.Value = i \ (50000 \ Me.ProgressBar1.Maximum)
'updUIare.Set()
Me.Label1.Refresh() '//send a paint msg to Label
End If
End Sub
End Class
You would see the Label's text change. The ProgressBar's value will update (or repaint) because the ProgressBar explicitly forces itself to update it's position.
Re: BeginInvoke - What I thought I knew
With the change the label does update but the progressbar didn't.
Re: BeginInvoke - What I thought I knew
Did you use my change for updating the ProgressBar? The logic you were using was i mod 100 which means that the value will go 0-100 and then reset (500 times). I found that was doing weird things on my pc until I changed it to what I posted.
Re: BeginInvoke - What I thought I knew
BeginInvoke() is the asynchronous version of Invoke(). Generally you are supposed to get the IAsyncResult it returns and at some point in the future call EndInvoke(), passing that IAsyncResult. See Calling Synchronous Methods Asynchronously. Since you never call EndInvoke(), the results are unpredictable. You shouldn't bother with BeginInvoke() in this case; it's for pushing synchronous methods to a worker thread when an API designer left you no options to do so. In this case you want to call the method synchronously.
That's one half of the problem. The other half is you run that loop as fast as possible and thus you push method calls to the UI thread as fast as possible. There's a famous episode of I Love Lucy where Lucy gets a job putting candy in boxes in a factory. At first, the candies come down the conveyor slowly, and she's pleased with how little effort is required. Soon, the manager ramps up the speed so high she cannot keep up, and the joke's in how she reacts. This is your poor UI thread; if you can chew through 1,000 iterations of the loop in a second you're asking the form to repaint itself 1,000 times in that same second. It can't keep up, so it looks like it's frozen.
When updating a UI from threaded code, it's best to throttle (I just wrote an explanation of this on another forum!) That Sleep() call in foo() would do the trick if you gave it a value of about 500 to limit updates to once every half-second. Of course, that means it's going to take forever to finish the loop. An alternative strategy would be to only update after a large amount of iterations:
Code:
For x As Integer = 1 To 50000
If x Mod 1000 = 0 Then
updUI(x)
End If
Next
That can still be a strain if the iterations are very fast, and it leads to inconsistent timing of updates on different machines. I prefer to use time-based throttling:
Code:
Private ReadOnly UpdateThreshold As TimeSpan = TimeSpan.FromMilliseconds(500)
Private Sub foo()
Dim updateTimer = Stopwatch.StartNew()
For x As Integer = 1 To 50000
If updateTimer.Elapsed > UpdateThreshold Then
updUI(x)
updateTimer.Restart()
End If
Next
' Final iteration needs to get pushed
updUI(50000)
End Sub
Obviously most machines are going to finish 50k iterations too fast for an update; you can bump it up to 50,000,000 to see it in action. Generally I pick 500ms or greater for the update interval because who can read updates faster than that?
It's a bit more complicated if each iteration has a result; then you have to store the intermediate results until it's time to update. Suppose you *really* wanted to see each number, maybe you're adding them to a list box or something:
Code:
Private ReadOnly UpdateThreshold As TimeSpan = TimeSpan.FromMilliseconds(500)
Private Sub foo()
Dim updates As New List(Of Integer)()
Dim updateTimer = Stopwatch.StartNew()
For x As Integer = 1 To 50000
If updateTimer.Elapsed > UpdateThreshold Then
updUI(updates.ToArray())
updateTimer.Restart()
updates.Clear()
Else
updates.Add(x)
End If
Next
' Final iteration needs to get pushed just in case it didn't...
updUI(updates.ToArray())
End Sub
Note the .ToArray() call when passing the list along to the updUI() method (which would obviously need a different parameter type.) The list is about to get cleared; if I sent the list the UI thread would find no items or, worse, the collection would be cleared while the UI thread enumerates it. You have to pass a copy of the list, and .ToArray()'s a convenient way to do so.
I've got a big fat 3-post tutorial about this stuff on another forum you frequent, but I've got a different handle there. You ought to give it a look. Async code's one of my specialties.
Re: BeginInvoke - What I thought I knew
It is my understanding from the documentation, that EndInvoke is not required, "You can call EndInvoke to retrieve the return value from the delegate, if neccesary, but this is not required. EndInvoke will block until the return value can be retrieved." In this case do I care about the return value since I am just updating the Form?
I do understand Begin / End invoke in general, but sometimes I don't understand the nuances of the Form.
Re: BeginInvoke - What I thought I knew
I'm superstitious about EndInvoke() and always call it even if the documentation says it's not required, but I don't blame you for following the documentation. It's probably because I've had to work with third-party stuff that doesn't do so well if you omit the EndInvoke(). I just tried it and it works fine without EndInvoke(), I bet I didn't have throttling implemented when I tried it out.
Either way, Invoke() is what I'd have reached for; it's the smallest hammer for this job.
Re: BeginInvoke - What I thought I knew
Quote:
Originally Posted by
Sitten Spynne
I'm superstitious about EndInvoke() and always call it even if the documentation says it's not required, but I don't blame you for following the documentation. It's probably because I've had to work with third-party stuff that doesn't do so well if you omit the EndInvoke(). I just tried it and it works fine without EndInvoke(), I bet I didn't have throttling implemented when I tried it out.
Either way, Invoke() is what I'd have reached for; it's the smallest hammer for this job.
That has been my experience with Controls, Invoke works best.