簡體   English   中英

如果程序立即失敗,則無法運行MailboxProcessor第一個循環

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

我有一個命令定期運行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 :我只是想知道是否有人設想了更好的解決方案。

僅供參考,完整的代碼就在這個要點中

實際上,您希望程序在程序退出之前等待MailboxProcessor完成處理其所有消息隊列。 您的printfn "%s" ex.Message似乎正在工作,但它不能保證工作:如果MailboxProcessor在其隊列中有多個項目,則運行printfn函數的線程可能在MailboxProcessor的線程有時間通過​​所有項目之前完成它的消息。

我建議的設計是將printerAgent的輸入更改為DU,如下所示:

type printerAgentMsg =
    | Message of string
    | Shutdown

然后,當您希望打印機代理完成發送其消息時,請使用main函數中的MailboxProcessor.PostAndReply (並記下文檔中的用法示例)並將其發送給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()

最簡單的解決方案是使用普通(同步)函數來記錄而不是使用MailboxProcessor,或者在主函數的末尾使用一些日志記錄框架和刷新記錄器。 如果您想繼續使用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))

在main函數中記錄異常時,應使用同步備用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM