简体   繁体   English

如果程序立即失败,则无法运行MailboxProcessor第一个循环

[英]MailboxProcessor first loop can't run if program immediately fails

I have a command running a SFTP check periodically and logging the result to a file. 我有一个命令定期运行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  

It loops over a MailboxProcessor 它遍历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() 
    )

which is called to write the messages to the log 调用它来将消息写入日志

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

The file downloads are asynchronous , as well as the corresponding messages, but all appears to work well. 文件下载是异步的 ,以及相应的消息,但似乎都可以正常工作。

The problem is that - if, for some reasons, the sftp connection immediately fails, the MailboxProcessor has no time to log the exception message. 问题是 - 如果由于某些原因,sftp连接立即失败, MailboxProcessor就没有时间记录异常消息。

What I've tried to do - which is working indeed - was adding a printfn "%s" ex.Message before the end: I just wanted to know if someone envisions a better solution. 我试图做的 - 确实有效 - 在结束之前添加了一个printfn "%s" ex.Message :我只是想知道是否有人设想了更好的解决方案。

FYI, the full code is in this gist . 仅供参考,完整的代码就在这个要点中

In fact, what you want is for the program to wait until the MailboxProcessor has finished handling all of its message queue before the program exits. 实际上,您希望程序在程序退出之前等待MailboxProcessor完成处理其所有消息队列。 Your printfn "%s" ex.Message seems to be working, but it's not guaranteed to work: if the MailboxProcessor had multiple items in its queue, the thread running the printfn function might finish before the MailboxProcessor's thread had had time to get through all of its messages. 您的printfn "%s" ex.Message似乎正在工作,但它不能保证工作:如果MailboxProcessor在其队列中有多个项目,则运行printfn函数的线程可能在MailboxProcessor的线程有时间通过​​所有项目之前完成它的消息。

The design I would recommend is to change the input of your printerAgent to be a DU like the following: 我建议的设计是将printerAgent的输入更改为DU,如下所示:

type printerAgentMsg =
    | Message of string
    | Shutdown

Then when you want the printer agent to finish sending its messages, use MailboxProcessor.PostAndReply (and note the usage example in the docs) in the main function and send it the Shutdown message. 然后,当您希望打印机代理完成发送其消息时,请使用main函数中的MailboxProcessor.PostAndReply (并记下文档中的用法示例)并将其发送给Shutdown消息。 Remember that MailboxProcessor messages are queued: by the time it receives the Shutdown message, it will have already gone through the rest of the messages in the queue. 请记住,MailboxProcessor消息已排队:当它收到Shutdown消息时,它已经完成了队列中的其余消息。 So all it needs to do to handle the Shutdown message is to return a unit reply, and simply not call its loop again. 因此,处理Shutdown消息所需要做的就是返回一个unit回复,而不是再次调用它的循环。 And because you used PostAndReply rather than PostAndReplyAsync , the main function will block until the MailboxProcessor has finished doing all its work. 并且因为您使用PostAndReply而不是PostAndReplyAsync ,主函数将阻塞,直到MailboxProcessor完成所有工作。 (To avoid any chance of blocking forever, I'd recommend setting a timeout like 10 seconds in your PostAndReply call; the default timeout is -1, meaning wait forever). (为了避免永远阻塞的可能性,我建议在PostAndReply调用中设置10秒的超时;默认超时为-1,意味着永远等待)。

EDIT: Here's an example (NOT tested, use at own risk) of what I mean: 编辑:这是我的意思的一个例子(未经过测试,自担风险使用):

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()

Easiest solution would be to use normal (synchronous) function for logging instead of MailboxProcessor or use some logging framework and flush loggers in the end of the main function. 最简单的解决方案是使用普通(同步)函数来记录而不是使用MailboxProcessor,或者在主函数的末尾使用一些日志记录框架和刷新记录器。 If you want to keep using printingAgent you can implement "synchronous" mode like this: 如果您想继续使用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() 
    )

Which you would then use either asynchronously 然后你可以异步使用它

printerAgent.Post(Log "Message")

or synchronously 或同步的

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

You should use synchronous alternative when you log exception in the main function. 在main函数中记录异常时,应使用同步备用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM