2012-02-09 76 views
1

我發現這個片段:Monadic重試邏輯W/F#和異步?

http://fssnip.net/8o

但我的工作不僅與retriable功能,而且還具有異步這樣,我想知道我是如何使這種類型的正確。我有一小塊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 - >Async<'c>

+0

如果發生錯誤(行)? – Daniel 2012-02-09 21:04:19

+0

關於具體的錯誤,你的'Bind'應該把參數作爲一個元組(寫'x.Bind(expr,f)'而不是'x.Bind expr f')。這可能是原因。但是,它也不會使用'f',這是非常可疑的(並且你的'Return'類型是錯誤的)。 – 2012-02-09 21:19:34

回答

6

您的Bind操作行爲類似於一個正常的Bind操作async,所以你的代碼主要是通過async重新實現(或封裝)。但是,您的Return不具有正確的類型(它應該是'T -> Async<'T>),並且您的Delay也不同於async的正常Delay。一般來說,你應該從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 } 

如果你真正想實現的計算生成器,將隱含嘗試重試每單異步操作,那麼你可以寫類似下面的(這只是一個草圖,但它應該指向你在正確的方向):

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

我可以以某種方式將來自片段的重試<'a>與異步<'a>包裝在一起嗎? – Henrik 2012-02-10 10:55:59