当我使用 async{} 计算表达式运行此代码块时:
let tokenSource = new CancellationTokenSource()
let runAsync() =
async {
while true do
do! Async.Sleep(1000 * 1)
printfn "hello"
}
Async.Start(runAsync(), tokenSource.Token)
...然后运行
tokenSource.Cancel()
,执行过程被取消,正如预期的那样。
但是,当我使用 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)
...然后运行
tokenSource.Cancel()
,执行过程不会被取消。
为什么取消令牌对于 async{} 可以按预期运行,但对于 task{} 却不能?
这是设计使然。出于性能和其他原因,任务故意热启动,而
Async
是冷启动。可以为冷启动的异步操作提供取消令牌。任务开始后(在task
的情况下是立即开始的),就不能再给予CT了。
您需要的是
IcedTask 库中的
cancellableTask
。
请注意,在 Async
CE 中包含
task
是可以的。嵌套的
Async
可以像任何其他异步一样被取消(同样,可以将令牌传递给
Task.DeLay
,这对于这种情况将有效)。 另请注意,代码中的
Task.Run
是多余的,通常不应使用。调用
runTask()
(顺便说一句,不应该是 rec
)在内部已经调用了 Task.Run
(或等效的),所以你所做的就是将其包装在另一个任务中。由于 CT 不会自动传递给子任务,因此 CT 没有效果。还有一件事,如果您在现实世界的代码中确实需要 rec
,请注意任务(来自
task
CE)不是尾递归,而 async
是。这可能很快就会改变,因为在不久的将来会强烈考虑这种变化。