具有F#和异步的Monadic Retry逻辑?

时间:2012-02-09 20:47:12

标签: asynchronous f#

我找到了这个片段:

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<'a>,但此处的类型为'b - &gt; Async<'c>

1 个答案:

答案 0 :(得分:6)

您的Bind操作行为与Bind的正常async操作相似,因此您的代码主要是async上的重新实现(或包装)。但是,您的Return没有正确的类型(应该是'T -> Async<'T>),而您的Delay也不同于Delay的正常async。一般情况下,您应该从BindReturn开始 - 使用Run有点棘手,因为Run用于包装整个foo { .. }块,所以它没有给你通常很好的可组合性。

真实世界函数式编程中的F# specification和免费chapter 12都显示了实现这些操作时应遵循的常用类型,因此我在此不再重复。

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

 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 }

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

// 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) }