F# - 取消令牌在 async{} 中有效,但不适用于 task{}

4
当我使用 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# 交互式环境中运行了此实验,如果相关的话。 - Overlord Zurg
1个回答

5

这是有意为之的。出于性能和其他原因,任务被故意热启动,而Async则是冷启动。可以给冷启动的异步操作提供取消令牌。在任务启动后,对于task来说,它不能再被赋予CT。

你需要的是IcedTask库中的cancellableTask

请注意,在task CE内部使用Async是可以的。嵌套的Async可以像任何其他异步操作一样被取消(同样,将令牌传递给Task.DeLay也是可以的,这对于此场景是有效的)。

还要注意,你代码中的Task.Run是多余的,通常不应该使用。调用runTask()(顺便说一句,它不应该是rec)内部已经调用了Task.Run(或等效),所以你所做的就是将其包装在另一个任务中。由于CT不会自动传递给子任务,因此CT没有效果。

还有一件事,如果您在实际编码中需要rec,请注意任务(来自task CE)不是尾递归,而async是。


1
谢谢你提供 rec 的提示 - 我猜测对于 task 来说可能也是这种情况,所以我改变了两个例子,不使用递归来排除这种可能性。但是我忘记删除关键字了。 - Overlord Zurg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接