Probably I have here 2 questions instead of one, but anyway.
I'm implementing cooperative cancellation as here suggested. Here is my test code:
type Async with
static member Isolate(f : CancellationToken -> Async<'T>) : Async<'T> =
async {
let! ct = Async.CancellationToken
let isolatedTask = Async.StartAsTask(f ct)
return! Async.AwaitTask isolatedTask
}
let testLoop (ct: CancellationToken) = async {
let rec next ix =
if ct.IsCancellationRequested then ()
else
printf "%i.." ix
Thread.Sleep 10
next (ix+1)
next 1
}
let cancellationSource = new CancellationTokenSource()
let onDone () = printfn "!! DONE"
let onError _ = printfn "!! ERROR"
let onCancel _ = printfn "!! CANCEL"
Async.StartWithContinuations (Async.Isolate testLoop, onDone, onError, onCancel, cancellationSource.Token)
Thread.Sleep(100)
cancellationSource.Cancel ()
Thread.Sleep(500)
As you can see, I start async with done, cancel and error continuations. If I run that code as is, I'll get the following output:
1..2..3..4..5..6..7..8..!! DONE
If I slightly update the Isolate
method as follows:
static member Isolate(f : CancellationToken -> Async<'T>) : Async<'T> =
async {
let! ct = Async.CancellationToken
let isolatedTask = Async.StartAsTask(f ct)
let! x = Async.AwaitTask isolatedTask
x
}
I get the expected (by myself) output:
1..2..3..4..5..6..7..!! CANCEL
Why do we have such difference in the behavior?
Is it possible to abort the testLoop
, if it does not cancelled within some timeout?
The async
block checks for cancellation of the Async.CancellationToken
only before and after bind (written using let!
). This means that when the token gets cancelled, the workflow will only get cancelled when there is more work to be done.
It is also worth noting that isolatedTask
does not itself gets cancelled in this example, because it just terminates regularly (using if
).
In your case:
When you use just return!
, the task returns regularly, Async.AwaitTask
returns regularly and nothing is done afterwards so the workflow completes.
When you use let!
followed by return
, the task returns regularly and Async.AwaitTask
returns regularly, but then let!
checks for cancellation before running return
and this cancels the workflow.