2012-09-16 55 views
3

有誰知道「現有技術」關於以下主題:懶..但急於數據加載

  • 我有一個需要一些體面的時間來加載數據。他們是各種股票的歷史水平。
  • 我想以某種方式預加載它們,用我的應用程序
  • 時,但是爲了避免等待時間,在開始一個數據塊預加載它們讓我的應用程序沒有響應第一這不是用戶友好的

所以我會喜歡載入我的數據.... 除非用戶沒有請求任何與他已經打,在這種情況下,我想一點得到一點。所以它既不是'懶惰'也不是'渴望','你需要時'更'懶惰','儘可能地渴望',因此是縮寫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) 
+0

+1我認爲您的解決方案已經是相當不錯的!在回答中增加了一些想法,雖然... –

回答

4

我喜歡你的解決方案。我認爲使用代理來實現一些超時的後臺加載是一個很好的方式 - 代理可以很好地封裝可變狀態,所以它顯然是安全的,你可以很容易地編碼你想要的行爲。

我想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>,但會盡快從源獲取一定數量的元素並緩存它們(這樣當消費者要求價值時,消費者不必等待,只要消息來源足夠快地產生價值)。

+0

有趣的是,感謝您的答案。我會考慮它,並可能會提出改進fsharpx。我意識到我們習慣了靜態數據流和單向執行流。可能這就是代理商如此流行的原因。我剛剛在計劃中閱讀了「宣傳者」,這也打破了這個想法。 – nicolas