“Throttled” async download in F#

前端 未结 4 2047
逝去的感伤
逝去的感伤 2020-11-30 05:59

I\'m trying to download the 3000+ photos referenced from the xml backup of my blog. The problem I came across is that if just one of those photos is no longer available, the

4条回答
  •  刺人心
    刺人心 (楼主)
    2020-11-30 06:47

    Here's a variation on Tomas's answer, because I needed an agent which could return results.

    type ThrottleMessage<'a> = 
        | AddJob of (Async<'a>*AsyncReplyChannel<'a>) 
        | DoneJob of ('a*AsyncReplyChannel<'a>) 
        | Stop
    
    /// This agent accumulates 'jobs' but limits the number which run concurrently.
    type ThrottleAgent<'a>(limit) =    
        let agent = MailboxProcessor>.Start(fun inbox ->
            let rec loop(jobs, count) = async {
                let! msg = inbox.Receive()  //get next message
                match msg with
                | AddJob(job) -> 
                    if count < limit then   //if not at limit, we work, else loop
                        return! work(job::jobs, count)
                    else
                        return! loop(job::jobs, count)
                | DoneJob(result, reply) -> 
                    reply.Reply(result)           //send back result to caller
                    return! work(jobs, count - 1) //no need to check limit here
                | Stop -> return () }
            and work(jobs, count) = async {
                match jobs with
                | [] -> return! loop(jobs, count) //if no jobs left, wait for more
                | (job, reply)::jobs ->          //run job, post Done when finished
                    async { let! result = job 
                            inbox.Post(DoneJob(result, reply)) }
                    |> Async.Start
                    return! loop(jobs, count + 1) //job started, go back to waiting
            }
            loop([], 0)
        )
        member m.AddJob(job) = agent.PostAndAsyncReply(fun rep-> AddJob(job, rep))
        member m.Stop() = agent.Post(Stop)
    

    In my particular case, I just need to use it as a 'one shot' 'map', so I added a static function:

        static member RunJobs limit jobs = 
            let agent = ThrottleAgent<'a>(limit)
            let res = jobs |> Seq.map (fun job -> agent.AddJob(job))
                           |> Async.Parallel
                           |> Async.RunSynchronously
            agent.Stop()
            res
    

    It seems to work ok...

提交回复
热议问题