Introducing Asynchronous coding to VB.NET novice developers

Many developers, hobbyist and weekend coders rely on searching the web for solutions to a problem rather than first reading documentation where good solutions are too abstract or in the case of Windows Forms or WPF code samples are done in a window or form. These people tend to pick the easiest solution and roll with it.

A common question, how do I perform an operation and keep my application responsive? Solutions tend to range from Application.DoEvents to using a BackGroundWorker component with no mention of asynchronous solutions or an asynchronous solution is shown but like the first two are shown directly in a form.

The person asking tends not to care which is better but instead which is easier to implement because to the novice coder easier is always better.

If the following is shown and along with other code samples without using classes they tend not to use classes in the future.

Imports System.Threading
 
Public Class Form1
 
    Private _cancellationTokenSource As New CancellationTokenSource()
 
    Private Async Sub RunButton_Click(sender As Object, e As EventArgs) _
        Handles RunButton.Click
 
        IterationLabel.Text = ""
 
        If _cancellationTokenSource.IsCancellationRequested Then
            _cancellationTokenSource.Dispose()
            _cancellationTokenSource = New CancellationTokenSource()
        End If
 
        Try
 
            For index As Integer = 0 To 10
 
                Dim current = index + 1
 
                If _cancellationTokenSource.IsCancellationRequested Then
                    _cancellationTokenSource.Token.ThrowIfCancellationRequested()
                End If
 
                Await Task.Run(Async Function()
                                   Await Task.Delay(1000, _cancellationTokenSource.Token)
                                   BeginInvoke(New Action(
                                       Sub()
                                           IterationLabel.Text = $"{current}"
                                       End Sub))
                               End Function)
            Next
 
        Catch oce As OperationCanceledException
            ' Cancellation from code in stop button
            IterationLabel.Text = "Canceled"
            Exit Sub
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
 
        IterationLabel.Text = "Done"
 
    End Sub
 
    Private Sub StopButton_Click(sender As Object, e As EventArgs) _
        Handles StopButton.Click
 
        _cancellationTokenSource.Cancel()
 
    End Sub
End Class

Instead for those who know better need to introduce the novice developer to a solution in this case using classes and perhaps delegates. Something like the following which uses a class and delegate.

Imports System.Threading
 
Namespace Classes
    Public Class Operations
        Public Event OnMonitor As DelegatesSignatures.MonitorHandler
        ''' <summary>
        ''' Do nothing method to show how to cancel a Task via
        ''' CancellationTokenSource
        ''' </summary>
        ''' <param name="value">int value greater than 0</param>
        ''' <param name="token">Initialized cancellation token</param>
        ''' <returns></returns>
        Public Async Function Run(value As Integer, token As CancellationTokenAs Task(Of Integer)
 
            Dim currentIndex = 0
 
            Do While currentIndex <= value - 1
 
                OnMonitorEvent?.Invoke(New MonitorArguments(Nothing, currentIndex))
 
                currentIndex += 1
 
                Await Task.Delay(1, token)
 
                If token.IsCancellationRequested Then
                    token.ThrowIfCancellationRequested()
                End If
            Loop
 
            OnMonitorEvent?.Invoke(New MonitorArguments("Done", currentIndex))
 
            Return currentIndex
 
        End Function
 
    End Class
End Namespace

Namespace Classes

    Public Class DelegatesSignatures
        ''' <summary>
        ''' Provides a callback when working is being performed in <see cref="Operations.Run"/>
        ''' </summary>
        ''' <param name="arguments"><see cref="MonitorArguments"/></param>
        Public Delegate Sub MonitorHandler(arguments As MonitorArguments)
    End Class
End NameSpace

Namespace Classes

    Public Class MonitorArguments
        Inherits EventArgs
        ''' <summary>
        ''' Create an new instance of this class
        ''' </summary>
        ''' <param name="msg">Message to display when finished operation</param>
        ''' <param name="percent">Percentage done</param>
        Public Sub New(msg As String, percent As Integer)
 
            Message = msg
            PercentDone = percent
 
        End Sub
        ''' <summary>
        ''' Text for operation completed
        ''' </summary>
        ''' <returns></returns>
        Public ReadOnly Property Message() As String
 
        ''' <summary>
        ''' Current progress of operation
        ''' </summary>
        ''' <returns></returns>
        Public ReadOnly Property PercentDone() As Integer
 
    End Class
End Namespace

Imports System.Threading

Imports AsynchronousCancellationTokenSample.Classes
 
Public Class Form1
 
    Private _totalIterations As Integer = 500
    Private _cancellationTokenSource As New CancellationTokenSource()
 
    Private Sub Form1_Shown(sender As Object, e As EventArgsHandles Me.Shown
        progressBar1.Maximum = _totalIterations
    End Sub
    ''' <summary>
    ''' Start a do nothing process, pass the CancellationTokenSource Token
    ''' to the operation which permits the method Run to recognize cancellation
    ''' when invoking _cancellationTokenSource.Cancel() in the Cancel button.
    '''
    ''' Note the do nothing operation runs asynchronously which means the user
    ''' interface remains responsive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Async Sub RunButton_Click(sender As Object, e As EventArgs) _
        Handles RunButton.Click
 
        RunButton.Enabled = False
 
        If _cancellationTokenSource.IsCancellationRequested Then
            _cancellationTokenSource.Dispose()
            _cancellationTokenSource = New CancellationTokenSource()
        End If
 
        Dim ops = New Operations()
 
        '
        ' Subscribe to OnMonitor which uses a custom EventArg to return
        ' text when the operation is done and percent done during the
        ' operation
        '
        AddHandler ops.OnMonitor, AddressOf MonitorDoNothingOperation
 
        Try
 
            Dim resultValue = Await ops.Run(_totalIterations,
                                            _cancellationTokenSource.Token)
 
            MessageBox.Show($"resultValue = {resultValue}")
 
        Catch oce As OperationCanceledException
            '
            ' Land here from token.ThrowIfCancellationRequested()
            ' thrown in Run method from a cancel request in this
            ' form's Cancel button
            '
            MessageBox.Show("Operation cancelled")
        Catch ex As Exception
            '
            ' Handle any unhandled exceptions
            '
            MessageBox.Show(ex.Message)
        Finally
            '
            ' Success or failure reenable the Run button
            '
            RunButton.Enabled = True
        End Try
 
    End Sub
    ''' <summary>
    ''' Call back for OnMonitor event
    ''' </summary>
    ''' <param name="args"></param>
    Private Sub MonitorDoNothingOperation(args As MonitorArguments)
        '
        ' Set current progress for ProgressBar
        '
        progressBar1.Value = args.PercentDone
 
        '
        ' Use text to indicate we are done
        '
        If args.PercentDone = _totalIterations Then
            MessageBox.Show(args.Message)
        End If
 
    End Sub
    '
    ' Request cancellation of current operation
    '
    Private Sub CancelButton_Click(sender As Object, e As EventArgs) _
        Handles CancelButton.Click
 
        If Not RunButton.Enabled Then
            _cancellationTokenSource.Cancel()
            RunButton.Enabled = True
        End If
 
    End Sub
 
End Class

By taking time to write a easy to follow code sample hopefully instills better coding practices to the coder or at least they tuck the code sample away to study later.

Source code for C# and VB.NET



Comments

  1. need to study this one too. useful tips and you're right:
    for the long run, better have solid fundamentals early!

    ReplyDelete

Post a Comment

Popular posts from this blog

VB.NET Working with Delegate and Events

Coders asking questions in online forums

Sometimes VB.NET coders make me wonder (or crazy)