简体   繁体   English

f# 邮箱处理器 - 无需等待发送即可回复

[英]f# mailboxprocessor - replying without waiting for delivery

I'm using an agent (MailboxProcessor) to do some stateful processing where a response is needed.我正在使用代理 (MailboxProcessor) 在需要响应的地方进行一些有状态处理。

  • The caller posts a message using MailboxProcessor.PostAndAsyncReply调用者使用MailboxProcessor.PostAndAsyncReply发布消息
  • Within the agent, a response is given with AsyncReplyChannel.Reply在代理中,使用AsyncReplyChannel.Reply给出响应

However, I've discovered by poking around the f# source code that the agent will not process the next message until the the response is delivered.但是,我通过查看 f# 源代码发现,在传递响应之前,代理不会处理下一条消息。 This is a good thing in general.总的来说,这是一件好事。 But in my case, it is more desirable for the agent to keep processing messages than to wait for response delivery.但就我而言,代理继续处理消息比等待响应传递更可取。

Is it problematic to do something like this to deliver the response?做这样的事情来提供响应有问题吗? (Or is there a better alternative?) (或者有更好的选择吗?)

async { replyChannel.Reply response } |> Async.Start

I realize that this method does not guarantee that responses will be delivered in order.我意识到这种方法并不能保证响应会按顺序传递。 I'm okay with that.我没问题。

Reference example参考示例

// agent code
let doWork data =
    async { ... ; return response }

let rec loop ( inbox : MailboxProcessor<_> ) =
    async {
        let! msg = inbox.Receive()
        match msg with
        | None ->
            return ()

        | Some ( data, replyChannel ) ->
            let! response = doWork data
            replyChannel.Reply response (* waits for delivery, vs below *)
            // async { replyChannel.Reply response } |> Async.Start
            return! loop inbox
    }

let agent =
    MailboxProcessor.Start(loop)

// caller code
async {
    let! response =
        agent.PostAndAsyncReply(fun replyChannel -> Some (data, replyChannel))
    ...
}

FSharp.Control.AsyncSeq puts a friendlier face on top of mailbox processor. FSharp.Control.AsyncSeq在邮箱处理器之上放置了一个更友好的面孔。 Async Sequences are a bit easier to follow, however the default implement mapping parallel has the same issue as described, waiting for the prior element in the sequence to be mapped to retain the order.异步序列更容易遵循,但是默认实现映射并行具有与所述相同的问题,等待序列中的前一个元素被映射以保留顺序。

So I made a new function taht is just the original AsyncSeq.mapAsyncParallel, modified so that it no longer is a true map, since it's unordered, but it does map everything and the lazy seq does progress as work completes.所以我创建了一个新函数 taht 只是原始的 AsyncSeq.mapAsyncParallel,经过修改使其不再是真正的映射,因为它是无序的,但它确实映射了所有内容,并且懒惰的 seq 会随着工作的完成而进展。

Full Source for AsyncSeq.mapAsyncParallelUnordered AsyncSeq.mapAsyncParallelUnordered 的完整源代码

let mapAsyncParallelUnordered (f:'a -> Async<'b>) (s:AsyncSeq<'a>) : AsyncSeq<'b> = asyncSeq {
  use mb = MailboxProcessor.Start (fun _ -> async.Return())
  let! err =
    s 
    |> AsyncSeq.iterAsyncParallel (fun a -> async {
      let! b = f a
      mb.Post (Some b) })
    |> Async.map (fun _ -> mb.Post None)
    |> Async.StartChildAsTask
  yield! 
    AsyncSeq.replicateUntilNoneAsync (Task.chooseTask (err |> Task.taskFault) (async.Delay mb.Receive))
  }

Below is an example of how I use it in a tool that uses SSLlabs free and very slow api that can easily get overloaded.下面是我如何在使用 SSLlabs 免费且速度非常慢的 api 的工具中使用它的示例,该 api 很容易过载。 parallelProcessHost returns an lazy AsyncSeq that is generated by the webapi requests, So AsyncSeq.mapAsyncParallelUnordered AsyncSeq.toListAsync actually runs the requests and allows the console to printout the results as the come in, independent of the order sent. parallelProcessHost返回一个由 webapi 请求生成的惰性AsyncSeq ,因此AsyncSeq.mapAsyncParallelUnordered AsyncSeq.toListAsync实际运行请求并允许控制台在输入时打印结果,与发送的顺序无关。

Full Source 完整来源

let! es = 
    hosts
    |> Seq.indexed
    |> AsyncSeq.ofSeq
    |> AsyncSeq.map parallelProcessHost
    |> AsyncSeq.mapAsyncParallelUnordered AsyncSeq.toListAsync
    |> AsyncSeq.indexed
    |> AsyncSeq.map (fun (i, tail) -> (consoleN "-- %d of %i --- %O --" (i+1L) totalHosts (DateTime.UtcNow - startTime)) :: tail )
    |> AsyncSeq.collect AsyncSeq.ofSeq
    |> AsyncSeq.map stdoutOrStatus //Write out to console
    |> AsyncSeq.fold (|||) ErrorStatus.Okay

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM