使用带有回复通道的MailboxProcessor来创建按顺序返回值的有限代理

时间:2016-09-04 15:50:26

标签: multithreading asynchronous f# mailboxprocessor

基本上,我想将以下内容更改为有限的线程解决方案,因为在我的情况下,计算列表太大,产生的线程太多,我想用较少的线程来试验和测量性能。

// the trivial approach (and largely my current situation)
let doWork() = 
    [1 .. 10]
    |> List.map (fun i -> async { 
        do! Async.Sleep (100 * i)   // longest thread will run 1 sec
        return i * i                // some complex calculation returning a certain type
        })
    |> Async.Parallel
    |> Async.RunSynchronously       // works, total wall time 1s

我的新方法,这个代码是由this online snippet from Tomas Petricek借用/启发的(我测试过,它有效,但我需要它来返回一个值,而不是单位)。

type LimitAgentMessage = 
  | Start of Async<int> *  AsyncReplyChannel<int>
  | Finished

let threadingLimitAgent limit = MailboxProcessor.Start(fun inbox -> async {

    let queue = System.Collections.Generic.Queue<_>()
    let count = ref 0
    while true do
        let! msg = inbox.Receive() 
        match msg with 
        | Start (work, reply) -> queue.Enqueue((work, reply))
        | Finished -> decr count
        if count.Value < limit && queue.Count > 0 then
            incr count
            let work, reply = queue.Dequeue()
            // Start it in a thread pool (on background)
            Async.Start(async { 
                let! x = work 
                do! async {reply.Reply x }
                inbox.Post(Finished) 
            }) 
  })


// given a synchronous list of tasks, run each task asynchronously, 
// return calculated values in original order
let worker lst = 
    // this doesn't work as expected, it waits for each reply
    let agent = threadingLimitAgent 10
    lst 
    |> List.map(fun x ->            
        agent.PostAndReply(
            fun replyChannel -> Start(x, replyChannel)))

现在,有了这个,原始代码将成为:

let doWork() = 
    [1 .. 10]
    |> List.map (fun i -> async { 
        do! Async.Sleep (100 * i)   // longest thread will run 1 sec
        return i * i                // some complex calculation returning a certain type
        })
    |> worker       // worker is not working (correct output, runs 5.5s)

总而言之,输出是正确的( 计算并传播回复),但在(有限集合)线程中没有这样做。

我一直在玩一下,但是我觉得我错过了显而易见的事情(而且,谁知道,有人可能会喜欢有限线程邮箱处理器按顺序返回计算的想法。)

1 个答案:

答案 0 :(得分:3)

问题是对agent.PostAndReply的调用。 PostAndReply将阻止工作完成。在List.map内调用此选项将导致工作按顺序执行。一种解决方案是使用PostAndAsyncReply,它不会阻塞,也会返回一个异步句柄来获取结果。

let worker lst = 
    let agent = threadingLimitAgent 10
    lst 
    |> List.map(fun x ->            
        agent.PostAndAsyncReply(
            fun replyChannel -> Start(x, replyChannel)))
    |> Async.Parallel

let doWork() = 
    [1 .. 10]
    |> List.map (fun i -> async { 
        do! Async.Sleep (100 * i)  
        return i * i               
        })
    |> worker      
    |> Async.RunSynchronously

这当然只是一种可能的解决方案(让所有异步句柄回来并等待它们并行)。