我有一个命令定期运行 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 %s\nroot dir list" host  |> printerAgent.Post
        do! downloadDir local client ""
        sprintf "Done, disconnecting now" |> printerAgent.Post
        client.Disconnect()
    } |> Async.RunSynchronously

文件下载以及相应的消息都是异步的,但似乎都运行良好。

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

我试图做的 - 这确实有效 - 在最后添加一个 printfn "%s" ex.Message :我只是想知道是否有人设想了更好的解决方案。

仅供引用,完整代码在 this gist 中。

最佳答案

实际上,您希望程序在退出之前等待 MailboxProcessor 处理完其所有消息队列。您的 printfn "%s" ex.Message 似乎有效,但不能保证有效:如果 MailboxProcessor 的队列中有多个项目,则运行 printfn 函数的线程可能会在 MailboxProcessor 的线程有时间处理所有消息之前完成。

我建议的设计是将 printerAgent 的输入更改为 DU,如下所示:

type printerAgentMsg =
    | Message of string
    | Shutdown

然后,当您希望打印机代理完成其消息的发送时,请在 MailboxProcessor.PostAndReply 函数中使用 main (并注意文档中的用法示例)并向其发送 Shutdown 消息。请记住 MailboxProcessor 消息已排队:当它收到 Shutdown 消息时,它将已经通过队列中的其余消息。因此,处理 Shutdown 消息所需要做的就是返回一个 unit 回复,而不是再次调用它的循环。并且因为您使用了 PostAndReply 而不是 PostAndReplyAsync ,主函数将阻塞,直到 MailboxProcessor 完成其所有工作。 (为了避免永远阻塞的任何机会,我建议在您的 PostAndReply 调用中设置一个像 10 秒这样的超时;默认超时是 -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()

关于asynchronous - 如果程序立即失败,MailboxProcessor 第一个循环将无法运行,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55243768/

10-11 16:46