f# - 懒惰..但在 F# 中急切的数据加载器

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

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

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

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

    和类(class)
    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)
    

    最佳答案

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

    我想 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<'T>并返回一个新的 AsyncSeq<'T>但是尽快从源获取一定数量的元素并缓存它们(这样消费者在请求值时就不必等待,只要源可以足够快地产生值)。

    关于f# - 懒惰..但在 F# 中急切的数据加载器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12449100/

    相关文章:

    unit-testing - 当被测类型覆盖 ToString 时,无法运行 TestCaseSource 测试

    f# - FolderBrowserDialog 未在 fsharp 脚本中打开

    c# - 使用 Entity Framework 6.4 进行延迟加载时如何异步调用关系?

    swift - 在 swift3 中使用方法而不是 block 来初始化惰性变量

    ruby-on-rails - 为什么 Rails 尝试在生产环境中自动加载此文件?

    inheritance - 我可以避免为派生自接口(interface)的类编写样板代码吗?

    reflection - 使用代码引用构建 AST 与表达式树

    reactjs - 使用命名导入来响应延迟导入

    entity-framework - 如何在没有延迟加载的情况下默认加载 Entity Framework ICollection?

    sequelize.js - Sequelize 多对多 M :N relationship not functioning. 错误: 'SequelizeEagerLoadingError:${model1} is not associated to ${model2}'