关于使用 Hopac 创建 Alt

About Creating an Alt with Hopac

当我使用 Hopac 创建 Alt<unit>Alt.<functions> 时,总是或一次,它会导致我得到奇怪的否定确认结果。

但是如果我使用 async {<expression>} 创建 Alt<unit> 那么一切都会按预期进行。

open Hopac
open Hopac.Core
open Hopac.Infixes
open Hopac.Extensions

let pf m (s:int) = Alt.prepareFun <| fun _ ->
    Alt.always () ^=> fun _ ->
        job { 
            printfn "starting [%s] %d" m Thread.CurrentThread.ManagedThreadId
            Thread.Sleep s
            printfn "%s" m }
        |> Job.start

let delayedPrintn3 msg delayInMillis =
  Alt.prepareFun <| fun _ ->     
    async {
        printfn "starting [%s] %d" msg Thread.CurrentThread.ManagedThreadId
        do! Async.Sleep delayInMillis
    }
    |> Alt.fromAsync
    |> Alt.afterFun (fun _ -> printfn "%s" msg)

let na : (string -> int -> Alt<unit>) -> string -> string -> int -> Alt<unit> = fun ff s1 s2 i ->
    Alt.withNackJob <|
        fun nack ->        
            nack
            |> Alt.afterFun (fun () ->
                  printfn "%s" s1)
            |> Job.start 
            |> Job.map (fun _ -> ff s2 i)

let na11 = na delayedPrintn3 "1 canceled!!" "na11" 3
let na22 = na delayedPrintn3 "2 canceled!!" "na22" 0

let na33 = na pf "1 canceled!!" "na33" 3
let na44 = na pf "2 canceled!!" "na44" 0

na22 <|> na11 |> run
na33 <|> na44 |> run

结果是:

starting [na22] 18
starting [na11] 18
na22
1 canceled!!

starting [na33] 11
na33

但是我想得到相同的结果。使用 Alt.<function> 时有什么问题?

Hopac Alt 非常棘手,我花了一段时间才弄对。

当您return将Alt转换为prepareFun/prepareJob时,您会想要return一个没有的Alt一直致力于。在 pf 的示例中,您 returning Alt.always 这意味着此 Alt 始终致力于。因此,当调用 na33 <|> na44 |> run 时,这意味着 na33 已经提交并且不需要 运行 na44.

相比之下,delayedPrintn3 的示例使用的是 Async,如果您查看 https://github.com/Hopac/Hopac/blob/master/Docs/Alternatives.md

中的参考实现
open System.Threading

let asyncAsAlt (xA: Async<'x>) : Alt<'x> = Alt.withNackJob <| fun nack ->
  let rI = IVar ()
  let tokenSource = new CancellationTokenSource ()
  let dispose () =
    tokenSource.Dispose ()
    // printfn "Dispose"
  let op = async {
      try
        let! x = xA
        do rI *<= x |> start
        // do printfn "Success"
      with e ->
        do rI *<=! e |> start
        // do printfn "Failure"
    }
  Async.Start (op, cancellationToken = tokenSource.Token)
  nack
  >>- fun () ->
        tokenSource.Cancel ()
        // printfn "Cancel"
        dispose ()
  |> Job.start >>-.
  Alt.tryFinallyFun rI dispose

它正在创建一个 IVar (think of them as the same as TaskCompletionSource),稍后将在异步 op 启动后设置。因此,在您的示例中,您可以看到两者都已启动,因为它们的 IVar 尚未提交。

如果您正在寻找类似的实现,例如:

let pf2 m (s:int) = Alt.prepareJob <| fun _ ->
    let retVal = IVar<unit>()
    job { 
        printfn "starting [%s] %d" m Thread.CurrentThread.ManagedThreadId
        do! timeOutMillis s
        printfn "%s" m 
        do! IVar.fill retVal ()
    }
    |> Job.start
    >>-. retVal

其中 return 一个 IVar(这是一个 Alt)尚未提交。我不得不将睡眠时间增加到 100,以确保 Hopac 不会过快地投入到第一个睡眠时间。


let na55 = na pf2 "1 canceled!!" "na55" 100
let na66 = na pf2 "2 canceled!!" "na66" 0

starting [na55] 9
starting [na66] 9
na66
1 canceled!!