When I run this code-block using the async{} computation expression:
let tokenSource = new CancellationTokenSource()
let runAsync() =
async {
while true do
do! Async.Sleep(1000 * 1)
printfn "hello"
}
Async.Start(runAsync(), tokenSource.Token)
...and then run tokenSource.Cancel(), the executing process is cancelled, as expected.
However, when I run this extremely similar code-block using task{}:
let tokenSource = new CancellationTokenSource()
let rec runTask() =
task {
while true do
do! Task.Delay(1000 * 1)
printfn "hello"
}
let run () = runTask () :> Task
Task.Run(run, tokenSource.Token)
...and then run tokenSource.Cancel(), the executing process is NOT cancelled.
Why does the cancellation token function as expected for async{} but not for task{}?
That’s by design. For performance and other reasons, tasks are deliberately hot started, while
Asyncis cold-started. Cold-started asynchronous operations can be given a cancellation token. After a task is started, which is immediate in the case oftask, it cannot be given a CT anymore.What you need is
cancellableTaskfrom the IcedTask library.Note that it’s fine to have an
Asyncinside ataskCE. That nestedAsynccan be cancelled just like any other Async (similarly, it’s fine to pass the token toTask.DeLay, which will work for this scenario).Note also that
Task.Runin your code is redundant and should typically not be used. CallingrunTask()(which shouldn’t berecbtw) internally already callsTask.Run(or equivalent), so all you’re doing is wrapping it in another task. Since CTs are not automatically passed on to child tasks, the CT doesn’t have effect.And one more thing, if you do need
recin your real world code, be advised that tasks (fromtaskCE) are not tail recursive, whileasyncis. This may change soon, as this change is strongly considered in the near future.