假设我有一个同步昂贵的操作:
let SomeExpensiveOp():string=
System.Console.WriteLine"about to begin expensive OP"
System.Threading.Thread.Sleep(TimeSpan.FromSeconds 2.0)
System.Console.WriteLine"finished expensive OP"
"foo"
我将其作为异步作业包装:
let SomeExpensiveOpAs():Async<string>=async {
return SomeExpensiveOp()}
现在我想用这个昂贵的操作将它与其他两个结合起来:
let SomeExpensiveOpSeq():seq<Async<string>>=
let op = SomeExpensiveOpAs()
seq {
for some in [Bar(); Baz()] do
yield async {
let! prefix = op
let! someAfterWaiting = some
return (String.Concat (prefix, someAfterWaiting))
}
}
将其放入seq<Async<'T>>
的目的是能够以这种方式使用Async.Parallel
:
let DoSomething() =
let someAsyncOps = SomeExpensiveOpSeq() |> List.ofSeq
let newOp = SomeExpensiveOpAs()
let moreAsyncOps = (newOp::someAsyncOps)
let allStrings = Async.RunSynchronously(Async.Parallel moreAsyncOps)
for str in allStrings do
Console.WriteLine str
Console.WriteLine()
但是,这会使SomeExpensiveOp
执行三次。由于上面的newOp
调用,我希望第二次执行额外的时间,但我希望SomeExpensiveOpSeq
重用对SomeExpensiveOp的调用而不是调用它两次。如何实现SomeExpensiveOpSeq只调用SomeExpensiveOp一次并将其重用于后续结果?
答案 0 :(得分:2)
这里的关键观察是let!
每次调用异步表达式 - 没有任何缓存其结果。考虑这个示例,我们有expOp : Async<string>
,但我们在async
表达式中等待三次:
let expOp = SomeExpensiveOpAs()
async {
let! a = expOp
let! b = expOp
let! c = expOp
return [a;b;c]
} |> Async.RunSynchronously
about to begin expensive OP
finished expensive OP
about to begin expensive OP
finished expensive OP
about to begin expensive OP
finished expensive OP
val it : string list = ["foo"; "foo"; "foo"]
您可以看到每次都会评估async 昂贵的操作。如果您只想执行一次昂贵的操作,您可以完全评估/等待其结果并使用它而不是多次等待它:
let SomeExpensiveOpSeq():seq<Async<string>>=
let op = SomeExpensiveOpAs() |> Async.RunSynchronously
seq {
for some in [Bar(); Baz()] do
yield async {
let! someAfterWaiting = some
return (String.Concat (op, someAfterWaiting))
}
}
这仍然会导致昂贵的操作在您的代码中执行两次 - 一次在SomeExpensiveOpSeq
,另一次由于被moreAsyncOps
添加 - 但它可以进一步重构为单个调用。基本上,如果所有后续的异步操作依赖于这种昂贵的评估,为什么不先评估它一次,然后在必要时使用它的值:
let SomeExpensiveOpSeq op : seq<Async<string>>=
seq {
for some in [Bar(); Baz()] do
yield async {
let! someAfterWaiting = some
return (String.Concat (op, someAfterWaiting))
}
}
let DoSomething() =
let newOp = SomeExpensiveOpAs() |> Async.RunSynchronously
let someAsyncOps = SomeExpensiveOpSeq newOp |> Async.Parallel |> Async.RunSynchronously
let allStrings = newOp::(List.ofArray someAsyncOps)
for str in allStrings do
Console.WriteLine str
Console.WriteLine()
> DoSomething();;
about to begin expensive OP
finished expensive OP
foo
foobar
foobaz