我正在尝试创建一个异步工作流,其中有一个主异步循环,它在每个循环中执行一个异步子块。我希望这个异步子块可以取消,但是当它取消时,我不要想要取消主循环。我希望它继续在do! subBlock
之后的行。
我在Async
中看到的唯一方法即使具有可接受的签名(取CancellationToken
,返回可转换为async
的内容)Async.StartAsTask
,但是取消时似乎挂起;在下面,它打印"取消"然后别的什么。
open System
open System.Threading
open System.Threading.Tasks
// runs until cancelled
let subBlock =
async {
try
while true do
printfn "doing it"
do! Async.Sleep 1000
printfn "did it"
finally
printfn "cancelled!"
}
[<EntryPoint>]
let main argv =
let ctsRef = ref <| new CancellationTokenSource()
let mainBlock =
//calls subBlock in a loop
async {
while true do
ctsRef := new CancellationTokenSource()
do! Async.StartAsTask(subBlock, TaskCreationOptions.None, (!ctsRef).Token)
|> Async.AwaitTask
printfn "restarting"
}
Async.Start mainBlock
//loop to cancel CTS at each keypress
while true do
Console.ReadLine() |> ignore
(!ctsRef).Cancel()
0
有没有办法做到这一点?
答案 0 :(得分:1)
这里发生的事情是,当您的子任务被取消时,OperationCanceledException
也会关闭您的mainBlock
。我能够通过使用它来实现它:
let rec mainBlock =
async {
ctsRef := new CancellationTokenSource()
let task = Async.StartAsTask(subBlock, TaskCreationOptions.None, (!ctsRef).Token) |> Async.AwaitTask
do! Async.TryCancelled(task, fun e ->
(!ctsRef).Dispose()
printfn "restarting"
Async.Start mainBlock)
}
取消任务后,在取消处理程序中显式重新启动mainBlock
。您需要为其添加#nowarn "40"
,因为在其定义中使用了mainBlock
。另请注意令牌源上的处理。
您可以在two threads中找到有关此问题的更多信息(也许是StartCatchCancellation
)形式的更好解决方案。
答案 1 :(得分:1)
启动和取消工作的调用者是否是异步也不会真正影响此问题,因为工作人员是通过其明确指定的取消令牌进行管理的。
Asyncs有三个延续:正常的一个,可以返回一个值,一个用于例外,一个用于取消。有多种方法可以将取消延续添加到异步,例如Async.OnCancel
,Async.TryCancelled
或一般Async.FromContinuations
,其中包括异常情况。这是一个具有所需输出的程序:
let rec doBlocks () =
async { printfn "doing it"
do! Async.Sleep 1000
printfn "did it"
do! doBlocks () }
let rec runMain () =
use cts = new CancellationTokenSource()
let worker = Async.TryCancelled(doBlocks (), fun _ -> printfn "Cancelled")
Async.Start(worker, cts.Token)
let k = Console.ReadKey(true)
cts.Cancel()
if k.Key <> ConsoleKey.Q then runMain ()
如果runMain
是异步,这也适用。在这个简单的例子中,你也可以打印出#34;取消&#34;消息本身。
我希望这会有所帮助。我不认为对如何构建程序有一般性的答案;这取决于具体的用例。