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 CancellationToken) As 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 EventArgs) Handles 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.
need to study this one too. useful tips and you're right:
ReplyDeletefor the long run, better have solid fundamentals early!