4

次の主題に関する「先行技術」を知っている人はいますか。

  • 読み込みにかなりの時間がかかるデータがあります。それらは様々な株の歴史的なレベルです。
  • アプリを使用する際のレイテンシーを回避するために、何らかの方法でそれらをプリロードしたいと思います
  • ただし、開始時にそれらを1つのチャンクにプリロードすると、最初にアプリが応答しなくなり、ユーザーフレンドリーではなくなります

だから私は自分のデータをロードしたくない....ユーザーが何も要求しておらず、すでに持っているもので遊んでいない限り、その場合は少しずつ取得したいと思います。つまり、「怠惰」でも「熱心」でもありません。「必要なときに怠惰」であり、「できるときに熱心」であるため、頭字語は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)
4

1 に答える 1

4

私はあなたの解決策が好きです。エージェントを使用してタイムアウト付きのバックグラウンドロードを実装するのは良い方法だと思います。エージェントは可変状態をうまくカプセル化できるため、明らかに安全であり、必要な動作を非常に簡単にエンコードできます。

非同期シーケンスは別の有用な抽象化かもしれないと思います(私が正しければ、それらは最近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>、ソースから特定の数の要素をできるだけ早く取得してキャッシュする(消費者がそうしないようにする)部分的に熱心なエバリュエーターです。ソースが十分に速く値を生成できる限り、値を要求するときに待機する必要があります)。

于 2012-09-16T19:03:42.650 に答える