【问题标题】:porting a dequeue - add loop from C# to F#移植出队 - 将循环从 C# 添加到 F#
【发布时间】:2020-07-08 19:42:00
【问题描述】:

我正在尝试移植这个简单的循环:

    var messages = new List<string>();
    while (_MessageQueue.TryDequeue(out var message))
        messages.Add(message);

消息队列是并发的。它用于一个模块,该模块将来自多个线程的消息排入队列,然后由单个线程处理。

是否有一种惯用的 F# 方法来执行出列/添加循环?

【问题讨论】:

    标签: c# f#


    【解决方案1】:

    F# 有几种实现并发的方法,包括对agent-based programming 的良好支持,因此您所做的惯用 F# 版本很可能实际上不会使用并发队列,而是基于代理或其他一些架构。

    但是,要回答您关于循环的具体问题 - 由于巧妙地使用了 whileout var,C# 版本非常简洁。在 F# 中,您可以调用 TryDequeue 作为返回 bool 和值的方法(这样我们可以避免突变)。我会将它与递归序列表达式一起使用:

    let mq = System.Collections.Concurrent.ConcurrentQueue<int>()
    
    let rec readAll () = seq {
      let succ, msg = mq.TryDequeue() 
      if succ then
          yield msg
          yield! readAll() }
    
    let messages = readAll() |> List.ofSeq
    

    readAll 函数定义了一个调用TryDequeue 的序列(IEnumerable),如果操作成功,它使用yield msg 将消息添加到结果中,然后使用@987654331 递归地尝试读取更多消息@。

    【讨论】:

    • 感谢您的回答;除了产量,我什么都懂! (相对于产量)。你能扩展一下吗?
    • 嗨,'屈服!'使输出变平,就像一个内置的折叠/减少,产生没有“!”的“readAll()”,将返回类型更改为“int list list”而不是“int list”,之前的“yield msg”会需要更改为“yield [msg]”以满足返回类型,....yield!保持列表“平坦”
    【解决方案2】:

    这是一个直接的转换:

        open System.Collections.Concurrent
    
        let _MessageQueue = ConcurrentQueue<string>()
        let messages = ResizeArray<string>()
        let mutable continueLooping = true
        while continueLooping do
            let success, message = _MessageQueue.TryDequeue()
            if success then messages.Add(message)
            continueLooping <- success
    

    【讨论】:

    • 是的,它是;感谢您的回答,它是一个直接的 c#->f# 端口,但我正在寻找一些惯用的东西;我正在学习 f#,所以我一直在寻找新的结构来学习。
    【解决方案3】:

    虽然这个问题已经有一个公认的答案,但我想使用库函数 Seq.unfold 贡献一个替代实现:

    let getAllMessages (mq : _ ConcurrentQueue) =
        mq |> Seq.unfold (fun q ->
            match q.TryDequeue () with
            | true, m -> Some (m, q)
            | _ -> None)
    
    let messages = getAllMessages _MessageQueue |> Seq.toList
    

    不确定它在内部是否与 Tomas 的解决方案一样复杂(甚至更复杂),但我发现它简短、易于理解且优雅。

    【讨论】:

    • 原则上我同意这很优雅,但它可能更简单,因为您不需要将任何有意义的状态传递给下一次迭代。如果你用 Seq.unfold (fun () -&gt; match mq.TryDequeue() with true, m -&gt; Some (m, ()) | _ -&gt; None) () 替换你的函数体怎么办?
    • @kaefer 我在这里看不到任何有意义的区别。我将队列传递给 lambda,以避免依赖于外部数据而关闭。
    【解决方案4】:

    我为咯咯笑提供了几个额外的设计选项。常用部分:

    open System.Collections.Concurrent
    
    type Message = { I: int }
    
    let queue = ConcurrentQueue<Message>()
    

    drain1 调用 queue.GetEnumerator(),它的条件是它在初始请求时返回一个快照。快照基本上与 C# 版本中的竞争条件相同。

    let drain1 () = queue |> Seq.toList
    

    drain2 返回一个数组,同样是初始请求时的快照。以防万一您有幸更改返回类型。

    let drain2 () = queue.ToArray()
    

    这是 TryQueue 的惯用返回示例,它避免了“out”参数,因此它不是 C# 所做/所做的可变值。

    let example () =
        let (success, message) = queue.TryDequeue()
        () // ...
    

    最后,一个递归构建的自终止序列。

    let drain3 () =
    
        let rec drain () = seq {
            let success, message = queue.TryDequeue()
            if success then
                yield message
                yield! drain()
            }
    
        drain() |> Seq.toList
    

    (适用标准互联网保修。)

    【讨论】:

      【解决方案5】:

      @Scott Hutchinson's answer上翻拍

      我会定义一个高效、直接的助手来封装突变和循环:-

      [<AutoOpen>]
      module ConcurrentQueueExtensions =
          type System.Collections.Concurrent.ConcurrentQueue<'T> with
              member this.Drain() =
                  let buffer = ResizeArray(this.Count)
                  let mutable more = true
                  while more do
                      match this.TryDequeue() with
                      | true, req -> buffer.Add req
                      | false, _ -> more <- false
                  buffer.ToArray()
      

      甚至将通用助手留在 C# 中:

      class ConcurrentQueueExtensions
      {
          public static T[] Drain<T>(this System.Collections.Concurrent.ConcurrentQueue<T> that)
          {
              var buffer = new List<T>(that.Count);
              while (that.TryDequeue(out var req))
                  buffer.Add(req);
              return buffer.ToArray();
          }
      }
      

      然后在不混合范式的情况下应用它:

      let messages = queue.Drain()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-30
        • 1970-01-01
        • 1970-01-01
        • 2012-10-07
        • 1970-01-01
        • 2011-03-25
        相关资源
        最近更新 更多