Vlad Vladimir Hercules
Vlad Vladimir Hercules

Reputation: 1859

Issue with updating the form using Async/Wait

In my application, I call a process to update software - which is stored within its own class. Even thou I have am Async/Wait and debug.print is returning a message within frmUpdate.ReportProgress() for some reason the progress bar in the update form is not updating...


Class

Namespace software

  Public Class updater 

    Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)

        Dim progressIndicator = New Progress(Of Integer)(AddressOf ReportProgress)
        Await SetProgressBar(progressIndicator, 100)
        Return True
    End Function

    Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
        myProgress.Report(counter)
    End Function

    Private Function ReportProgress(ByVal count As Integer)
        frmUpdate.ReportProgress(count)
    End Function
  End Class

End Namespace

Code for the Form Below

Public Class frmUpdate

    Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
        updater.UpdateSoftware(url, downloadFolder) 
    End Function

    Public Function ReportProgress(ByVal myInt As Integer) 
        ProgressBar1.Value = myInt
        Debug.Print(ProgressBar1.Value)
    End Function
End Class

Upvotes: 1

Views: 682

Answers (1)

Steven Doggart
Steven Doggart

Reputation: 43743

Async and Await do not inherently make things multi-threaded. There's good reason for that. Not all asynchronous tasks are multi-threaded. For instance, if you want to have some task wait until the user clicks a button, you don't need that task to be on it's own thread. That would be very wasteful to have a separate thread just sitting there looping while it waits for the button to be clicked. In a situation like that, you'd just have the click event of the button signal the task to continue/complete. Then, the task can simply block execution until that signal is received. Since it's the case that making all asynchronous tasks multi-threaded is wasteful, starting a task in a new thread is left as a separate optional action.

Simply adding the Async keyword to your method signature does nothing to make your method run on a separate thread. In fact, it technically doesn't even make your method asynchronous. It will only be asynchronous if you call Await inside the method somewhere. So, there is no appreciable difference between your method:

Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
    myProgress.Report(counter)
End Function

And this:

Private Sub SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer)
    myProgress.Report(counter)
End Sub

Both methods execute immediately when they are called and both block execution in whatever method called them until they are complete. If myProgress, whatever that is, provides an asynchronous version of the Report method (i.e. an equivalent method that returns a Task), then you want to call that and await on it:

Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
    Await myProgress.ReportAsync(counter)
End Function

If no such asynchronous alternative exists, then you can force it to be asynchronous by starting it in its own thread:

Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
    Await Task.Run(Sub() myProgress.ReportAsync(counter))
End Function

However, in this case, I'm quite certain, given the name of the method, that it doesn't really need to be asynchronous at all. The real issue is that whatever the long-running work you're doing in UpdateSoftware is, it's not being done with Await, so it's what's blocking when it shouldn't be. But, since you didn't show that code, it's hard to give a good example. Consider something like this:

Public Class updater 
    Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)
        Await Task.Run(AddressOf LongRunningWork)
        Return True
    End Function

    Private Sub LongRunningWork()
        ' Do something that takes a while
        For i As Integer = 1 to 100
            ReportProgress(i)
            Thread.Sleep(100)
        Next
    End Sub

    Private Sub ReportProgress(count As Integer)
        frmUpdate.BeginInvoke(Sub() frmUpdate.ReportProgress(count))
    End Function
End Class

Note that in the ReportProgress method, I have it calling BeginInvoke (though Invoke would work too) on the form to get it to execute that form's method on the UI thread rather than on the task's thread. That's always important to do. Anytime you are updating the UI, you always need to invoke back the the UI thread to do the updating, otherwise you'll get cross-thread exceptions.

Also, you aren't using Await in the event handler either, which you ought to do. Technically, it works either way, but if you start adding exception handling, you'll find out quickly it makes a big difference:

Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
    Await updater.UpdateSoftware(url, downloadFolder) 
End Function

For more information about Async/Await (Microsoft calls it the Task-based Asynchronous Pattern, or TAP for short), see the documentation. TAP is a really powerful tool. It makes asynchronous code very easy to read and understand. But, despite appearing simple on the surface, it still requires a good understanding of the underlying concepts to use it properly. If you are feeling uncertain about it, you may want to try using the BackgroundWorker component, as I suggested in my answer to your previous question, since it's a little less magical and may be easier for you to see what's happening where and why.

Upvotes: 3

Related Questions