【问题标题】:Lazy.. but eager data loader in F#懒惰..但 F# 中的急切数据加载器
【发布时间】:2012-09-16 17:33:54
【问题描述】:

有谁知道关于以下主题的“现有技术”:

  • 我的数据需要相当长的时间才能加载。它们是各种股票的历史水平。
  • 我想以某种方式预加载它们,以避免在使用我的应用时出现延迟
  • 但是,在开始时将它们预加载到一个块中会使我的应用首先无响应,这对用户不友好

所以我想加载我的数据.... 除非用户没有请求任何内容并使用他已经拥有的东西,在这种情况下我会喜欢一点一点的得到。所以它既不是“懒惰”也不是“渴望”,更多的是“当你需要时懒惰”和“当你可以时渴望”,因此缩写为 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)

【问题讨论】:

  • +1 我认为您的解决方案已经相当不错了!虽然在答案中添加了一些想法......

标签: f# lazy-loading eager-loading agent mailboxprocessor


【解决方案1】:

我喜欢你的解决方案。我认为使用代理来实现一些带有超时的后台加载是一个很好的方法 - 代理可以很好地封装可变状态,所以它显然是安全的,你可以很容易地编码你想要的行为。

我认为asynchronous sequences 可能是另一个有用的抽象(如果我是正确的,它们现在在 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&lt;'T&gt; 并返回一个新的AsyncSeq&lt;'T&gt;,但会尽快从源中获取一定数量的元素,并且缓存它们(以便消费者在请求值时不必等待,只要源可以足够快地产生值)。

【讨论】:

  • 有意思,谢谢你的回答。我会考虑并可能会建议对 fsharpx 进行改进。我意识到我们已经习惯了静态数据流和单向执行流。可能这就是代理如此流行的原因。我刚刚阅读了方案中的“传播者”,这也有点打破了这个想法。
猜你喜欢
  • 1970-01-01
  • 2017-07-14
  • 2019-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多