async {... AsyncAdd ...}和async {... AsyncAdd ...}有什么区别?

时间:2019-08-26 04:25:57

标签: f#

在以下代码中,do! ag.AsyncAdd (Some i)ag.AsyncAdd (Some i)(在功能enqueue()中)均起作用。它们之间有什么区别?看来do! ...会使入队和出队电话更加混杂吗?怎么样?

open FSharpx.Control

let test () =
    let ag = new BlockingQueueAgent<int option>(500)

    let enqueue() = async { 
        for i = 1 to 15 do 
            // ag.AsyncAdd (Some i) // works too
            do! ag.AsyncAdd (Some i) 
            printfn "=> %d" i }

    async {
        do! [ for i = 1 to 10 do yield enqueue() ] 
            |> Async.Parallel |> Async.Ignore
        for i = 1 to 5 do ag.Add None
    } |> Async.Start

    let rec dequeue() =
        async {
            let! m = ag.AsyncGet()
            match m with
            | Some v ->
                printfn "<= %d" v
                return! dequeue()
            | None -> 
                printfn "Done" 
        }

    [ for i = 1 to 5 do yield dequeue() ] 
    |> Async.Parallel |> Async.Ignore |> Async.RunSynchronously
    0

2 个答案:

答案 0 :(得分:6)

在任何F#计算表达式中,任何以!结尾的关键字都倾向于表示“根据此块的规则专门处理此问题”。例如,在async { }块中,let!关键字的意思是“等待结果,然后将结果分配给此变量”,而do!关键字的意思是“等待此异步操作,但扔掉结果,请勿将其分配给任何内容”。如果您使用do!关键字,那么您等待该操作的结果。

因此,在do!函数中使用enqueue关键字,您将执行以下十五次:

  • 开始进行AsyncAdd操作
  • 等待它完成
  • 打印“ => 1”(或2或3 ...)

没有关键字do!,您正在执行以下操作:

  • 尽快开始15项AsyncAdd操作
  • 将每个按钮踢开后,打印“ => 1”(或2或3 ...)

听起来您还没有完全了解F#的计算表达式在幕后的工作方式。我建议阅读Scott Wlaschin的优秀站点以获取更多的理解:首先https://fsharpforfunandprofit.com/posts/concurrency-async-and-parallel/,然后是https://fsharpforfunandprofit.com/series/computation-expressions.html,这样,当您阅读第二系列文章时,便会基于一些现有知识。

答案 1 :(得分:3)

来自FSharpx source code(请参阅评论):

    /// Asynchronously adds item to the queue. The operation ends when
    /// there is a place for the item. If the queue is full, the operation
    /// will block until some items are removed.
    member x.AsyncAdd(v:'T, ?timeout) = 
      agent.PostAndAsyncReply((fun ch -> AsyncAdd(v, ch)), ?timeout=timeout)

当不使用do!时,如果队列已满(在构造函数中声明的队列中有500个项目),则不会阻塞排队线程。因此,当您将循环更改为更大的数目时,您将所有入队线程的所有迭代中类型为AsyncAdd的消息(在后台FSharpx使用MailboxProcessor-检查此类的文档)发送到MailboxProcessor队列中。这会减慢另一个操作agent.Scan:

        and fullQueue() = 
            agent.Scan(fun msg ->
              match msg with 
              | AsyncGet(reply) -> Some(dequeueAndContinue(reply))
              | _ -> None )
  • 因为队列中有很多AsyncAdd和AsyncGet。

以防万一,当您放进去!在AsyncAdd之前,当队列中有500个项目时,您的排队线程将被阻塞,并且不会为MailboxProcessor生成其他消息,因此agent.Scan将快速运行。当出队线程接收一个项目并且它们的数量变为499时,新的入队线程将被唤醒并添加新的项目,然后转到下一个循环迭代,将新的AsyncAdd消息放入MailboxProcessor中,然后再次进入睡眠状态,直到出队时刻为止。因此,MailboxProcessor不会被一个入队线程的所有迭代的消息AsyncAdd所淹没。注意:项目队列和MailboxProcessor消息队列是不同的队列。