首页 > 解决方案 > 如何让 Promise 变得懒惰?

问题描述

open System
open System.Threading
open Hopac
open Hopac.Infixes

let hello what = job {
  for i=1 to 3 do
    do! timeOut (TimeSpan.FromSeconds 1.0)
    do printfn "%s" what
}

run <| job {
  let! j1 = Promise.start (hello "Hello, from a job!")
  do! timeOut (TimeSpan.FromSeconds 0.5)
  let! j2 = Promise.start (hello "Hello, from another job!")
  //do! Promise.read j1
  //do! Promise.read j2
  return ()
}

Console.ReadKey()
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!

这是Hopac 文档中的示例之一。从我在这里可以看到,即使我没有显式调用Promise.read j1Promise.read j2函数仍然运行。我想知道是否可以推迟执行承诺的计算,直到它们实际运行?或者我应该使用lazy传播惰性值的目的吗?

查看文档,Hopac 的承诺似乎应该是懒惰的,但我不确定这种懒惰应该如何表现出来。

标签: concurrencypromisef#hopac

解决方案


为了演示懒惰,请考虑以下示例。

module HopacArith

open System
open Hopac

type S = S of int * Promise<S>

let rec arith i : Promise<S> = memo <| Job.delay(fun () ->
    printfn "Hello"
    S(i,arith (i+1)) |> Job.result
    )

let rec loop k = job {
    let! (S(i,_)) = k
    let! (S(i,k)) = k
    printfn "%i" i
    Console.ReadKey()
    return! loop k
    }

loop (arith 0) |> run
Hello
0
Hello
1
Hello
2

如果没有记住这些值,则每次按下 enter 时,Hello每次迭代都会打印两个 s。memo <|如果删除,则可以看到此行为。

还有一些值得进一步说明的观点。的目的Promise.start并不是专门为某些工作获得记忆行为。Promise.start类似于Job.start如果您使用let!或绑定一个值>>=,它不会阻塞工作流,直到工作完成。但是,与 相比Job.startPromise.start确实提供了一个选项,可以通过绑定嵌套值来等待计划作业完成。与常规 .NET 任务不同Job.start且类似的是,可以从使用 .NET 启动的并发作业中提取值Promise.start

最后,这是我在玩 Promise 时发现的一个有趣的花絮。事实证明,将 aJob转换为 an 的一个好方法Alt是将其转换为Promisefirst 然后向上转换。

module HopacPromiseNonblocking

open System
open Hopac
open Hopac.Infixes

Alt.choose [
    //Alt.always 1 ^=>. Alt.never () // blocks forever
    memo (Alt.always 1 ^=>. Alt.never ()) :> _ Alt // does not block
    Alt.always 1 >>=*. Alt.never () :> _ Alt // same as above, does not block
    Alt.always 2
    ]
|> run
|> printfn "%i" // prints 2

Console.ReadKey()

取消注释第一种情况会导致程序永远阻塞,但是如果您首先记住表达式,那么如果使用常规替代方案,就有可能获得回溯行为。


推荐阅读