[英]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
:我只是想知道是否有人设想了更好的解决方案。
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.