对于这种情况,推荐的F#模式是什么?

时间:2011-08-18 06:37:32

标签: asynchronous f# agent

我的情况类似于以下情况:

let mutable stopped = false

let runAsync() = async {
    while not stopped do
        let! item = fetchItemToProcessAsync
        match item with
        | Some job -> job |> runJobAsync |> Async.Start
        | None -> do! Async.Sleep(1000)
}

let run() = Async.Start runAsync
let stop() =
    stopped <- true

现在,当调用stop方法时,我必须停止从DB读取更多项目,并等待当前开始完成的项目,然后再从此函数返回。

实现这一目标的最佳方法是什么?我正在考虑使用计数器(使用互锁API),并在计数器达到0时从stop方法返回。

如果有其他方法可以实现这一目标,我将不胜感激。我有一种感觉,我可以在这里使用代理,但我不确定是否有任何可用的方法来完成代理或如果我仍然必须编写我的自定义逻辑来确定作业已完成执行。

2 个答案:

答案 0 :(得分:1)

看看actor-based patterns and MailboxProcessor

基本上你可以把它想象成一个异步队列。如果您使用运行列表(以Async.StartChildAsync.StartAsTask开头)作为MailboxProcessor内部循环的参数,则可以通过wait或CancellationToken正常处理关闭)

以下是我放在一起的快速示例:


type Commands = 
    | RunJob of Async
    | JobDone of int
    | Quit of AsyncReplyChannel

type JobRunner() =
    let processor =
        MailboxProcessor.Start (fun inbox ->
            let rec loop (nextId, jobs) = async {
                let! cmd = inbox.Receive()
                match cmd with
                | Quit cb ->
                    if not (Map.isEmpty jobs) 
                    then async {
                            do! Async.Sleep 100
                            inbox.Post (Quit cb)}
                        |> Async.Start
                        return! loop (nextId, jobs)
                    else 
                        cb.Reply()
                        return ()
                | JobDone id ->
                    return! loop (nextId, jobs |> Map.remove id)
                | RunJob job ->
                    let runJob i = async {
                        do! job
                        inbox.Post (JobDone i)
                    }
                    let! child = Async.StartChild (runJob nextId)
                    return! loop (nextId+1, jobs |> Map.add nextId child)
            }
            loop (0, Map.empty))
    member jr.PostJob(job) = processor.Post (RunJob job)
    member jr.Quit() = processor.PostAndReply(fun cb -> Quit cb)

let postWaitJob (jobRunner : JobRunner) time =
    let job = async {
        do! Async.Sleep time
        printfn "sleept for %d ms" time }
    jobRunner.PostJob job

let testRun() =
    let jr = new JobRunner()
    printfn "starting jobs..."
    [10..-1..1] |> List.iter (fun i -> postWaitJob jr (i*1000))
    printfn "sending quit"
    jr.Quit()
    printfn "done!"

嗯......编辑器遇到了一些问题:当我使用管道返回操作符时,它只会杀死很多代码... grrr

简短说明:正如您所见,我总是为内部循环提供下一个免费作业ID和一个Id-&gt; AsyncChild作业地图。 (你当然可以实现其他/更好的解决方案 - 在这个例子中不需要Map,但你可以使用命令“Cancell JobNr”或其他任何方式扩展) “完成作业”消息仅用于从此映射中删除作业 退出只检查映射是否为空 - 如果不需要额外的工作并且邮箱处理器退出(return()) - 如果它不为空,则启动新的Async-Child,等待100ms,然后重新发送Quit-Message RunJob相当简单 - 它只是将给定的作业用JobDone的一个帖子链接到MessabeboxProcessor中,并使用更新的值重新调用recursivley循环(nextId是一个up,一个新的Job映射到旧的nextId)

答案 1 :(得分:1)

在fssnip.net上查看此snippet。这是您可以使用的通用作业处理器。