代表AsyncSeq生成器中的错误

时间:2018-08-15 19:51:44

标签: f# msmq lazy-sequences

我最近一直在从事与一些MSMQ消息队列接口的项目。 System.Messaging API十分悲惨,尤其是如果您希望像对待可观察的消息序列一样使用队列,因此我整理了一个包装器API以使其更可口,但是我仍然要解决的问题之一具有代表接收消息时发生的错误。

有些表示为MessageQueueException的错误实际上只是预期的结果,例如在调用PeekReceive超时之前没有消息到达队列。 。由于这些阻塞了MSMQ中永远无法返回的调用,因此基本上需要使用超时来调用它们,因此我想处理某些消息队列异常,然后继续等待下一条消息(如果未取消),但是我不这样做希望我的AsyncSeq仅在出现更严重的错误(例如DTC故障)时永远等待。

有问题的函数是下面的Msmq.subscribe函数(仅包括相关代码):

#r "System.Messaging"
#r "System.Transactions"
#r "FSharp.Control.AsyncSeq.dll"

open FSharp.Control
open System
open System.IO
open System.Messaging
open System.Text
open System.Threading
open System.Transactions

type MsmqFormatName =
| MsmqDirect of string

type MsmqQueue =
    private {
        MsmqFormatName: MsmqFormatName
        MessageQueue: MessageQueue
    } member queue.FormatName = 
        match queue.MsmqFormatName with
        | MsmqDirect queuePath -> sprintf "DIRECT=OS:%s" queuePath
      member queue.Queue = queue.MessageQueue

type MsmqTransaction =
| Local of MessageQueueTransaction
| Distributed
| Ambient
| AutoCommit

type MsmqMode =
| NonTransactional
| Transactional of MsmqTransaction

type MsmqError =
| MsmqReceiveTimedOut
| MsmqException of MessageQueueException
| NonMsmqException of exn

type ICommittable =
    abstract member Commit: unit -> Result<unit, MsmqError>
    abstract member Abort: unit -> Result<unit, MsmqError>

type MsmqMessage =
| Committed of Message
| Uncommitted of Message * ICommittable


[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
module Msmq =
    let private tryAsync f =
        async {
            try
                let! message = f
                return Ok message
            with
            | :? MessageQueueException as ex ->
                return
                    if ex.MessageQueueErrorCode <> MessageQueueErrorCode.IOTimeout
                    then Error MsmqReceiveTimedOut
                    else Error (MsmqException ex)
            | ex -> 
                return Error (NonMsmqException ex)
        }

    let receive timeout mode (msmq: MsmqQueue) = 
        let queue = msmq.Queue
        match mode with
        | NonTransactional -> 
            Async.FromBeginEnd ((fun _ -> queue.BeginReceive(timeout)), queue.EndReceive)
        | Transactional tx ->
            match tx with
            | Local transaction -> 
                async { return queue.Receive(timeout, transaction) }
            | Distributed ->
                async {
                    use transaction = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled)
                    let message = queue.Receive(timeout, MessageQueueTransactionType.Automatic)
                    transaction.Complete()
                    return message
                }
            | Ambient ->
                async { return queue.Receive(timeout, MessageQueueTransactionType.Automatic) }
            | AutoCommit ->
                async { return queue.Receive(timeout, MessageQueueTransactionType.Single) }
        |> tryAsync    

    let private tryMsmqOp f =
        try f() |> Ok
        with | :? MessageQueueException as ex -> Error (if ex.MessageQueueErrorCode = MessageQueueErrorCode.IOTimeout then MsmqReceiveTimedOut else MsmqException ex)
             | ex -> Error (NonMsmqException ex)

    let subscribe (cancellation: CancellationToken) mode msmq =
        let timeout = TimeSpan.FromSeconds 10.0        
        let rec getMessages () =
            asyncSeq {
                let! result = 
                    match mode with
                    | NonTransactional -> async.Bind(receive timeout mode msmq, Result.bind (Committed >> Ok) >> async.Return)
                    | Transactional tx ->
                        match tx with
                        | Local transaction -> 
                            let committable = 
                                { new ICommittable with
                                    member __.Commit () = tryMsmqOp transaction.Commit
                                    member __.Abort () = tryMsmqOp transaction.Abort
                                }                                        
                            async.Bind(receive timeout mode msmq, Result.bind (fun message -> Ok (Uncommitted (message, committable))) >> async.Return)
                        | Distributed ->
                            async {
                                let transaction = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled)
                                let! result = receive timeout (Transactional Ambient) msmq
                                match result with
                                | Ok message ->
                                    let committable =
                                        {new ICommittable with
                                            member __.Commit () = tryMsmqOp (transaction.Complete >> transaction.Dispose)
                                            member __.Abort () = tryMsmqOp (transaction.Dispose)
                                        }
                                    return Ok <| Uncommitted (message, committable)
                                | Error e -> 
                                    return Error e
                            }
                        | Ambient ->
                            async {
                                use transaction = 
                                    let tx = new MessageQueueTransaction()
                                    tx.Begin()
                                    tx
                                let! result = receive timeout (Transactional Ambient) msmq
                                match result with
                                | Ok message ->
                                    let committable =
                                        {new ICommittable with
                                            member __.Commit () = tryMsmqOp transaction.Commit
                                            member __.Abort () = tryMsmqOp transaction.Dispose
                                        }
                                    return Ok <| Uncommitted (message, committable)
                                | Error e ->
                                    return Error e
                            }
                        | AutoCommit -> async.Bind(receive timeout mode msmq, Result.bind (Committed >> Ok) >> async.Return)

                match result with
                | Ok message -> yield message
                | Error _ -> () // Note:  Swallows Errors

                if not cancellation.IsCancellationRequested
                then yield! getMessages ()
            }
        getMessages ()       

我的MSMQ客户端的完整代码可用here

对于单个receive操作,我返回一个Result<Message, MsmqError>,其中MsmqError类型表示类似超时的事物与其他异常不同。在这种情况下,事务管理由调用方完成。但是,对于返回subscribe的{​​{1}}操作,我使用了一种称为AsyncSeq的类型,它表示消息及其关联事务的组合,因为通常具有每个消息的事务处理,在许多情况下,直到消费者完全处理了消息后才应提交事务处理。我也可以将MsmqMessage包装在MsmqMessage中,但是将实际消息埋在Result中的MsmqMessage中的元组中似乎很不方便。另外,我可以检查Result生成器中的错误并引发异常,但是我真的不喜欢在我的单操作函数返回asyncSeq类型时抛出基于序列的函数的想法。

客户端的想法是像这样使用它:

Result

在延迟计算的序列中生成下一个元素期间,尤其是在使用""".\private$\test.msmq""" |> FormatName.direct |> Msmq.connect AllProperties |> Msmq.subscribe cancellation.Token (Transactional Distributed) |> AsyncSeq.iterAsyncParallel (fun message -> async { match message with | Committed msg -> printfn "%A: Received Committed Message %A" DateTime.Now msg.Id | Uncommitted (msg, tx) -> printfn "%A: Received Uncommitted Message %A" DateTime.Now msg.Id // This is where you'd actually process the message match tx.Commit() with | Ok _ -> printfn "%A: Committed Message %A" DateTime.Now msg.Id | Error e -> printfn "%A: Error Committing Message %A -- %A" DateTime.Now msg.Id e }) 库时,在这种情况下如何处理错误?

0 个答案:

没有答案