邮箱处理器第一个循环无法运行,如果程序立即失败



我有一个命令,定期运行SFTP检查并将结果记录到文件。

let logPath = Path.Combine(config.["SharedFolder"],timestamp)
let sw = new StreamWriter(logPath,true)
//...
[<EntryPoint>]
let main argv = 
    try 
        sftpExample config.["SharedFolder"] config.["SFTPFolder"] 22 "usr" "pswd" |> ignore
    with 
    | ex -> 
        ex.Message |> printerAgent.Post
        printfn "%s" ex.Message // <- NOTICE THIS LINE
    sw.Close()
    sw.Dispose()
0  

它通过MailboxProcessor

循环
let printerAgent = MailboxProcessor.Start(fun inbox-> 
    // the message processing function
    let rec messageLoop() = async{        
        // read a message
        let! msg = inbox.Receive()
        // process a message
        sw.WriteLine("{0}: {1}", DateTime.UtcNow.ToShortTimeString(), msg)
        printfn "%s" msg
        // loop to top
        return! messageLoop()  
        }
    // start the loop 
    messageLoop() 
    )

被称为将消息写入日志

let sftpExample local host port username (password:string) =
    async {
        use client = new SftpClient(host, port, username, password)
        client.Connect()
        sprintf "Connected to %snroot dir list" host  |> printerAgent.Post
        do! downloadDir local client ""   
        sprintf "Done, disconnecting now" |> printerAgent.Post
        client.Disconnect()
    } |> Async.RunSynchronously

文件下载是 asynchronous 以及相应的消息,但所有消息似乎都可以很好地工作。

问题是 - 如果出于某些原因,SFTP连接立即失败,则MailboxProcessor没有时间记录异常消息。

我尝试做的事情 - 确实有效 - 在结束之前添加了printfn "%s" ex.Message:我只是想知道某人是否设想一个更好的解决方案。

fyi,完整的代码在此要点中。

实际上,您想要的是让程序等到邮箱普通处理器完成所有消息队列之前,请在程序退出之前处理其所有消息队列。您的printfn "%s" ex.Message似乎正在工作,但不能保证可以正常工作:如果邮箱制处人的队列中有多个项目,则运行printfn函数的线程可能会在邮箱普罗沃克人的线程有时间通过所有消息之前完成。

我建议的设计是将您的printerAgent的输入更改为DU,如以下内容:

type printerAgentMsg =
    | Message of string
    | Shutdown

然后,当您希望打印机代理完成其消息时,请使用main功能中的MailboxProcessor.PostAndReply(并在文档中注意使用示例)并将其发送给Shutdown消息。请记住,邮箱普通邮件已排队:到收到Shutdown消息时,它将已经通过队列中的其余消息了。因此,处理Shutdown消息所需要做的就是返回unit回复,而根本不再次调用其循环。而且,由于您使用的是PostAndReply而不是PostAndReplyAsync,因此主函数将阻止,直到邮箱制处人完成所有工作。(为避免永远阻止的机会,我建议您在PostAndReply调用中设置一个超时的时间;默认超时为-1,表示永远等待)。

)。

编辑:这是我的意思是一个示例(未进行测试,使用自身风险):

type printerAgentMsg =
    | Message of string
    | Shutdown of AsyncReplyChannel<unit>
let printerAgent = MailboxProcessor.Start(fun inbox-> 
    // the message processing function
    let rec messageLoop() = async{        
        // read a message
        let! msg = inbox.Receive()
        // process a message
        match msg with
        | Message text ->
            sw.WriteLine("{0}: {1}", DateTime.UtcNow.ToShortTimeString(), text)
            printfn "%s" text
            // loop to top
            return! messageLoop()
        | Shutdown replyChannel ->
            replyChannel.Reply()
            // We do NOT do return! messageLoop() here
        }
    // start the loop 
    messageLoop() 
    )
let logPath = Path.Combine(config.["SharedFolder"],timestamp)
let sw = new StreamWriter(logPath,true)
//...
[<EntryPoint>]
let main argv = 
    try 
        sftpExample config.["SharedFolder"] config.["SFTPFolder"] 22 "usr" "pswd" |> ignore
    with 
    | ex -> 
        ex.Message |> Message |> printerAgent.Post
        printfn "%s" ex.Message // <- NOTICE THIS LINE
    printerAgent.PostAndReply( (fun replyChannel -> Shutdown replyChannel), 10000)  // Timeout = 10000 ms = 10 seconds
    sw.Close()
    sw.Dispose()

最简单的解决方案是使用正常的(同步)函数来登录而不是邮箱制作者或在主函数末尾使用一些记录框架和齐平记录器。如果要继续使用printingAgent,则可以实现这样的"同步"模式:

type Msg =
    | Log of string
    | LogAndWait of string * AsyncReplyChannel<unit>
let printerAgent = MailboxProcessor.Start(fun inbox -> 
    let processLogMessage logMessage =
        sw.WriteLine("{0}: {1}", DateTime.UtcNow.ToShortTimeString(), logMessage)
        printfn "%s" logMessage
    let rec messageLoop() = async{        
        let! msg = inbox.Receive()
        match msg with 
        | Log logMessage ->
            processLogMessage logMessage
        | LogAndWait (logMessage, replyChannel) ->
            processLogMessage logMessage
            replyChannel.Reply()
        return! messageLoop()  
        }
    messageLoop() 
    )

然后您将使用异步

使用它
printerAgent.Post(Log "Message")

或同步

printerAgent.PostAndReply(fun channel -> LogAndWait("Message", channel))

在主函数中记录异常时,应使用同步替代方案。

相关内容

  • 没有找到相关文章

最新更新