取消F#异步工作流程的子块

时间:2014-12-20 02:22:16

标签: f# f#-async

我正在尝试创建一个异步工作流,其中有一个主异步循环,它在每个循环中执行一个异步子块。我希望这个异步子块可以取消,但是当它取消时,我不要想要取消主循环。我希望它继续在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

有没有办法做到这一点?

2 个答案:

答案 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.OnCancelAsync.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;消息本身。

我希望这会有所帮助。我不认为对如何构建程序有一般性的答案;这取决于具体的用例。

相关问题