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 Async is cold-started. Cold-started asynchronous operations can be given a cancellation token. After a task is started, which is immediate in the case of task, it cannot be given a CT anymore.
What you need is cancellableTask from the IcedTask library.
Note that it’s fine to have an Async inside a task CE. That nested Async can be cancelled just like any other Async (similarly, it’s fine to pass the token to Task.DeLay, which will work for this scenario).
Note also that Task.Run in your code is redundant and should typically not be used. Calling runTask() (which shouldn’t be rec btw) internally already calls Task.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 rec in your real world code, be advised that tasks (from task CE) are not tail recursive, while async is. This may change soon, as this change is strongly considered in the near future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With