Results 1 to 8 of 8

Thread: BeginInvoke - What I thought I knew

  1. #1

    Thread Starter
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,897

    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.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

  2. #2
    Master Of Orion ForumAccount's Avatar
    Join Date
    Jan 2009
    Location
    Canada
    Posts
    2,802

    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.

  3. #3

    Thread Starter
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,897

    Re: BeginInvoke - What I thought I knew

    With the change the label does update but the progressbar didn't.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

  4. #4
    Master Of Orion ForumAccount's Avatar
    Join Date
    Jan 2009
    Location
    Canada
    Posts
    2,802

    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.

  5. #5
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  6. #6

    Thread Starter
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,897

    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.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

  7. #7
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  8. #8

    Thread Starter
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,897

    Re: BeginInvoke - What I thought I knew

    Quote Originally Posted by Sitten Spynne View Post
    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.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width