F# - 取消令牌适用于异步{},但不适用于任务{}

问题描述 投票:0回答:1

当我使用 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{} 却不能?

f# task f#-async
1个回答
5
投票

这是设计使然。出于性能和其他原因,任务故意热启动,而

Async
是冷启动。可以为冷启动的异步操作提供取消令牌。任务开始后(在
task
的情况下是立即开始的),就不能再给予CT了。

您需要的是

IcedTask 库中的 
cancellableTask

请注意,在

Async

 CE 中包含 
task
 是可以的。嵌套的 
Async
 可以像任何其他异步一样被取消(同样,可以将令牌传递给 
Task.DeLay
,这对于这种情况
将有效)。 另请注意,代码中的

Task.Run

是多余的,通常不应使用。调用

runTask()
(顺便说一句,不应该是
rec
)在内部已经调用了
Task.Run
(或等效的),所以你所做的就是将其包装在另一个任务中。由于 CT 不会自动传递给子任务,因此 CT 没有效果。
还有一件事,如果您在现实世界的代码中确实需要 

rec

,请注意任务(来自

task
CE)不是尾递归,而
async
是。这可能很快就会改变,因为
在不久的将来会强烈考虑这种变化

© www.soinside.com 2019 - 2024. All rights reserved.