-2

This appears to be a common problem but I am unable to find a solution yet. I have checked this in SO Cancelling a Task is throwing an exception

My caller:

    Private Async Sub btnTestTimer_Click(sender As Object, e As EventArgs) Handles btnTest.Click
    _cts = New CancellationTokenSource()
    Try
        Await Task.Run(AddressOf TestCancellationAsync).ConfigureAwait(False)
    Catch cx As OperationCanceledException
        MsgBox(String.Format("The following error occurred: {0}", cx.Message), MsgBoxStyle.Critical)
    Catch ex As Exception
        MsgBox(String.Format("The following error occurred: {0}", ex.Message), MsgBoxStyle.Critical)
    End Try
End Sub

My task is here

    Private Async Function TestCancellationAsync() As Task
        'Launch a dummy timer which after some time will itself cancel a token and throw
        Dim tmr As New System.Timers.Timer(1000)
        AddHandler tmr.Elapsed, AddressOf OnTimerElapsed
        tmr.Enabled = True
    End Function

And the timer function that cancels and throws out is

    Private Sub OnTimerElapsed(sender As Object, e As ElapsedEventArgs)
        Dim tmr As System.Timers.Timer = CType(sender, System.Timers.Timer)
        tmr.Enabled = False
        Task.Delay(5000) 'After 5 seconds simulate a cancellation
        _cts.Cancel() //This is just to cancel from within the timer, actually the cancellation to _cts will happen from another caller which is not shown here
        _cts.Token.ThrowIfCancellationRequested()
    End Sub

The actual program with the async task and the cancellation is not shown here to keep the example concise while still being able to replicating the problem.

The business requirement is that on clicking a button, an asynchronous task will be launched, which will open up several async functions. One of them would launch a timer which will keep checking the _cts token state and cancel if required. If such a cancellation happens from outside on the _cts token the timer will throw a cancellation exception

Things I have tried:

  • I have handled the OperationCancelled exception but still its not going there.
  • I have unchecked Tools-Options-Debug-General-Enable just my code to see if its just Visual Studio. But still it is reported as a PDB unhandled exception
  • I have run the exe from outside, as expected, it crashed due to the unhandled exception

Do let me know what I am doing here wrong. My caller awaits for the task to finish - since a timer is running from inside the task, I will expect that the task is not finished and any exceptions raised would be caught.

Kallol
  • 264
  • 1
  • 12

1 Answers1

0

I think in this case, the timer was the issue. Creating and having a timer to fire from within a task and then throw cancellation exception from within the timer processing method does not work because as soon as the timer is created and enabled, the task returns without waiting for the timer to complete. This means that the TestCancellationAsync method does not wait for the timer code to fire and hold the task. It returns immediately to the caller. The caller inside btnTestTimer_Click thinks the task has returned and comes out of the try and ends the method. This means there are no valid event handlers to catch the exception thrown by the timer. This results in the unhandled exception.

The solution would be to simulate a timer by using an infinite loop with relevant delays and calling the timer code from inside it without creating a timer object.

So the TestCancellationAsync should be changed to look like this

    Private Async Function TestCancellationAsync() As Task
    'Simulate a timer behaviour
    While True
        Await DoWorkAsync().ConfigureAwait(False)
        Await Task.Delay(1000).ConfigureAwait(False)
    End While
End Function

And the OnTimerElapsed which actually is the worker function can be then changed to

Private Async Function DoWorkAsync() As Task
    'Do work code
    _cts.Token.ThrowIfCancellationRequested()
End Sub

Now the if _cts is cancelled from outside, it is being caught.

This solves the current problem.

Kallol
  • 264
  • 1
  • 12