【问题标题】:Monadic Retry logic w/ F# and async?带 F# 和异步的 Monadic 重试逻辑?
【发布时间】:2012-02-09 20:47:11
【问题描述】:

我找到了这个 sn-p:

http://fssnip.net/8o

但我不仅使用可重试函数,还使用异步函数,我想知道如何正确地制作这种类型。我有一小块 retryAsync monad 我想用它来代替异步计算,但它包含重试逻辑,我想知道如何组合它们?

type AsyncRetryBuilder(retries) =
  member x.Return a = a               // Enable 'return'
  member x.ReturnFrom a = x.Run a
  member x.Delay f = f                // Gets wrapped body and returns it (as it is)
                                       // so that the body is passed to 'Run'
  member x.Bind expr f = async {
    let! tmp = expr
    return tmp
    }
  member x.Zero = failwith "Zero"
  member x.Run (f : unit -> Async<_>) : _ =
    let rec loop = function
      | 0, Some(ex) -> raise ex
      | n, _        -> 
        try 
          async { let! v = f()
                  return v }
        with ex -> loop (n-1, Some(ex))
    loop(retries, None)

let asyncRetry = AsyncRetryBuilder(4)

消费代码是这样的:

module Queue =
  let desc (nm : NamespaceManager) name = asyncRetry {
    let! exists = Async.FromBeginEnd(name, nm.BeginQueueExists, nm.EndQueueExists)
    let beginCreate = nm.BeginCreateQueue : string * AsyncCallback * obj -> IAsyncResult
    return! if exists then Async.FromBeginEnd(name, nm.BeginGetQueue, nm.EndGetQueue)
            else Async.FromBeginEnd(name, beginCreate, nm.EndCreateQueue)
    }

  let recv (client : MessageReceiver) timeout =
    let bRecv = client.BeginReceive : TimeSpan * AsyncCallback * obj -> IAsyncResult
    asyncRetry { 
      let! res = Async.FromBeginEnd(timeout, bRecv, client.EndReceive)
      return res }

错误是:

此表达式的类型应为 Async&lt;'a&gt;,但这里的类型为 'b -> Async&lt;'c&gt;

【问题讨论】:

  • 哪里出现错误(行)?
  • 关于具体错误,你的Bind 应该将参数作为一个元组(写x.Bind(expr, f) 而不是x.Bind expr f)。这大概就是原因。但是,它也根本不使用f,这是非常可疑的(并且您的Return 类型错误)。

标签: asynchronous f#


【解决方案1】:

您的Bind 操作的行为类似于async 的普通Bind 操作,因此您的代码主要是对async 的重新实现(或包装)。但是,您的 Return 没有正确的类型(应该是 'T -&gt; Async&lt;'T&gt;),并且您的 Delay 也不同于普通的 Delayasync。一般来说,您应该从BindReturn 开始——使用Run 有点棘手,因为Run 用于包装整个foo { .. } 块,因此它不会为您提供通常的良好可组合性。

Real-World Functional Programming 中的F# specification 和免费的chapter 12 都显示了在实现这些操作时应该遵循的常用类型,所以我不会在这里重复。

您的方法的主要问题是您尝试仅在Run 中重试计算,但您所指的重试构建器尝试重试使用let! 调用的每个单独操作。您的方法可能就足够了,但如果是这种情况,您只需实现一个尝试运行正常 Async&lt;'T&gt; 并重试的函数:

 let RetryRun count (work:Async<'T>) = async { 
   try 
     // Try to run the work
     return! work
   with e ->
     // Retry if the count is larger than 0, otherwise fail
     if count > 0 then return! RetryRun (count - 1) work
     else return raise e }

如果你真的想实现一个计算构建器,它会隐式地尝试重试每个异步操作,那么你可以编写如下内容(这只是一个草图,但它应该为你指明正确的方向):

// We're working with normal Async<'T> and 
// attempt to retry it until it succeeds, so 
// the computation has type Async<'T>
type RetryAsyncBuilder() =
  member x.ReturnFrom(comp) = comp // Just return the computation
  member x.Return(v) = async { return v } // Return value inside async
  member x.Delay(f) = async { return! f() } // Wrap function inside async
  member x.Bind(work, f) =
    async { 
      try 
        // Try to call the input workflow
        let! v = work
        // If it succeeds, try to do the rest of the work
        return! f v
      with e ->
        // In case of exception, call Bind to try again
        return! x.Bind(work, f) }

【讨论】:

  • 我能否以某种方式将来自 sn-p 的 Retry 包装为 Async?
猜你喜欢
  • 2013-12-21
  • 2011-05-17
  • 2021-10-23
  • 1970-01-01
  • 2012-10-20
  • 2016-11-08
  • 2022-12-06
  • 2021-11-18
  • 2012-09-24
相关资源
最近更新 更多