Reputation: 992
I have a query which takes a long time to return so I shove the function with returns a datatable into a function, within a task like so:
Dim tokenSource As CancellationTokenSource
Private Async Sub btnSomeThing() Handles btnSomething.Click
tokenSource = New CancellationTokenSource
Dim cancellationToken As CancellationToken = tokenSource.Token
Dim t As Task(Of DataTable) = Task.Run(Function()
Return someFunction.GetDataTable()
End Function,
cancellationToken)
Await t
Try
If t.Result.Rows.Count > 0 Then
' Display the datatable
End If
Catch e As Exception
If TypeOf e IsNot TaskCanceledException Then
MessageBox.Show("Error")
End If
Finally
tokenSource.Dispose()
End Try
End Sub
And I attempt to cancel the task by calling tokenSource.Cancel()
elsewhere.
However what I'm seeing is that when Return someFunction.GetDataTable()
eventually returns, the code continues to execute as if that task hasn't been cancelled.
I realise that someFunction.GetDataTable()
is going to continue until it's done as there's not a nice way to stop a database call once it's started, but when it does return shouldn't task t
throw TaskCanceledException and so the rest of the routine is skipped as part of the Try-Catch?
How should I be correctly cancelling t
?
EDIT:
Below is the code now. The exception from cancellationToken.ThrowIfCancellationRequested()
is not "caught" and so the application just enters breakmode. I have also tried calling cancellationToken.ThrowIfCancellationRequested()
within Task.Run
, after Await t
but all of these have the same effect. Once cancellationToken.ThrowIfCancellationRequested()
is called the application crashes.
Dim tokenSource As CancellationTokenSource
Private Async Sub btnSomeThing() Handles btnSomething.Click
tokenSource = New CancellationTokenSource
Dim cancellationToken As CancellationToken = tokenSource.Token
Dim t As Task(Of DataTable) = Task.Run(Function()
Return GetDataTable(cancellationToken)
End Function,
Try cancellationToken)
Await t
If t.Result.Rows.Count > 0 Then
' Display the datatable
End If
Catch ex As AggregateException
For Each item In ex.InnerExceptions
Debug.WriteLine(ex.Message & " " & item.Message)
Next
Finally
tokenSource.Dispose()
End Try
End Sub
Public Function GetDataTable (cancellationToken As CancellationToken)
Dim dt As DataTable
'fill dt from query with DataAdapter
cancellationToken.ThrowIfCancellationRequested()
Return dt
End Function
Upvotes: 1
Views: 643
Reputation: 2140
There is no way to just kill the task from the outside. That's why there is the concept of a CancellationToken
.
It's not enough just to pass that token to the task as you are doing in your code. You have to write your own logic inside that task to handle a cancellation request.
Since you are using your Task just to call one deeper function (someFunction.GetDataTable()
) you have to implement support for cancellation for this function as well. I.e. pass the cancellation token you got in the task into that function and then do something like this (Pseudocode):
Function GetDataTable(ct As CancellationToken)
ResultSet = Empty
While DatabaseTable.HasMoreRecords
ResultSet.Add DatabaseTable.ReadNext100Records()
If ct.IsCancellationRequested Then
' Do the logic you want to perform when the query should be cancelled
' e.g. close db connection or such things
' Then call this function to raise the Exception
ct.ThrowIfCancellationRequested()
End If
End While
Return ResultSet
End Function
So to say in words, create a loop, read only a small subset of rows and check whether cancellation is requested before reading the next records.
For reference have a look here
Edit, to answer your comment here: The reference link gives you a good example how to handle that thrown exception. To summarize, you have to wrap try-catch around awaiting the task:
Try
Await t
If t.Result.Rows.Count > 0 Then
' Display the datatable
End If
Catch e As Exception
If TypeOf e IsNot OperationCanceledException Then
MessageBox.Show("Error")
End If
Finally
tokenSource.Dispose()
End Try
Upvotes: 4