有人知道关于以下主题的"现有技术"吗:
- 我有一些数据需要相当长的时间才能加载。它们是各种股票的历史水平
- 我想以某种方式预加载它们,以避免在使用我的应用程序时出现延迟
- 然而,一开始就将它们预加载到一个块中会使我的应用程序首先没有响应,这对用户来说是不友好的
所以我想而不是加载我的数据除非用户没有请求任何内容,并且正在使用他已经拥有的内容,在这种情况下,我希望一点一点地获得。因此,它既不是"懒惰"也不是"渴望",更多的是"需要时懒惰"one_answers"可以时渴望",因此缩写为LWYNEWYC。
我做了以下这些似乎有效,但我只是想知道是否有一种公认的、幸运的方法来做这件事?
let r = LoggingFakeRepo () :> IQuoteRepository
r.getHisto "1" |> ignore //prints Getting histo for 1 when called
let rc = RepoCached (r) :> IQuoteRepository
rc.getHisto "1" |> ignore //prints Getting histo for 1 the first time only
let rcc = RepoCachedEager (r) :> IQuoteRepository
rcc.getHisto "100" |> ignore //prints Getting histo 1..100 by itself BUT
//prints Getting histo 100 immediately when called
和类
type IQuoteRepository =
abstract getUnderlyings : string seq
abstract getHisto : string -> string
type LoggingFakeRepo () =
interface IQuoteRepository with
member x.getUnderlyings = printfn "getting underlyings"
[1 .. 100] |> List.map string :> _
member x.getHisto udl = printfn "getting histo for %A" udl
"I am a historical dataset in a disguised party"
type RepoCached (rep : IQuoteRepository) =
let memoize f =
let cache = new System.Collections.Generic.Dictionary<_, _>()
fun x ->
if cache.ContainsKey(x) then cache.[x]
else let res = f x
cache.[x] <- res
res
let udls = lazy (rep.getUnderlyings )
let gethistom = memoize rep.getHisto
interface IQuoteRepository with
member x.getUnderlyings = udls.Force()
member x.getHisto udl = gethistom udl
type Message = string * AsyncReplyChannel<UnderlyingWrap>
type RepoCachedEager (rep : IQuoteRepository) =
let udls = rep.getUnderlyings
let agent = MailboxProcessor<Message>.Start(fun inbox ->
let repocached = RepoCached (rep) :> IQuoteRepository
let rec loop l =
async { try
let timeout = if l|> List.isEmpty then -1 else 50
let! (udl, replyChannel) = inbox.Receive(timeout)
replyChannel.Reply(repocached.getHisto udl)
do! loop l
with
| :? System.TimeoutException ->
let udl::xs = l
repocached.getHisto udl |> ignore
do! loop xs
}
loop (udls |> Seq.toList))
interface IQuoteRepository with
member x.getUnderlyings = udls
member x.getHisto udl = agent.PostAndReply(fun reply -> udl, reply)
我喜欢你的解决方案。我认为使用代理在超时的情况下实现一些后台加载是一种很好的方法——代理可以很好地封装可变状态,因此它显然是安全的,并且可以很容易地对您想要的行为进行编码。
我认为异步序列可能是另一个有用的抽象(如果我是正确的,它们现在在FSharpX中可用)。异步序列表示异步生成更多值的计算,因此它们可能是将数据加载程序与代码其余部分分离的好方法。
我认为在某个时刻您仍然需要一个代理来同步,但您可以使用异步序列很好地分离不同的关注点。
加载数据的代码可能看起来像这样:
let loadStockPrices repo = asyncSeq {
// TODO: Not sure how you detect that the repository has no more data...
while true do
// Get next item from the repository, preferably asynchronously!
let! data = repo.AsyncGetNextHistoricalValue()
// Return the value to the caller...
yield data }
此代码表示数据加载器,并将其与使用它的代码分离。从使用数据源的代理程序中,您可以使用AsyncSeq.iterAsync
来使用值并对它们执行操作。
对于iterAsync
,您指定为使用者的函数是异步。它可能会阻塞(即使用Sleep
),当它阻塞时,源(即您的加载程序)也会被阻塞。这是一种从消耗数据的代码中控制加载程序的非常好的隐式方法。
库中还没有的一个功能(但很有用)是一个部分热切的评估器,它获取AsyncSeq<'T>
并返回一个新的AsyncSeq<'T>
,但尽快从源中获取一定数量的元素并缓存它们(这样,消费者在请求值时就不必等待,只要源能够足够快地生成值)。