如果没有,是什么让我导出来实现接口的函数保持活动状态?
我正在 Haskell 中实现一个通知服务器,目前我有这样的东西,
startServer :: IORef Notifications -> IO ()
startServer notifications = do
client <- connectSession
reply <- requestName client "org.freedesktop.Notifications" [nameDoNotQueue]
export client "/org/freedesktop/Notifications" defaultInterface {
interfaceName = "org.freedesktop.Notifications",
interfaceMethods = [
autoMethod "GetServerInformation" getServerInformation,
autoMethod "GetCapabilities" getCapabilities,
makeMethod "Notify" (signature_ notifyInSig) (signature_ notifyOutSig) (notify notifications)
]
}
when (reply == NamePrimaryOwner) $ forever $ threadDelay oneSec
where
oneSec = 1000000
它是从一个更精简但独立的示例演变而来的,您可以在我的问题和接受的答案中找到它。
当时,
forever $ threadDelay oneSec
在那里,因为我直接在main
中进行实验,所以我不希望可执行文件返回,因为我需要时间通过notify-send
发送通知并查看notify
是否已存在确实在尽职尽责。
但是现在我的项目已经取得了一些进展,我不确定我是否还需要它。
上面代码目前的使用方式是这样的,
-- In the IO monad; not really in main, but it's ok to think of it as main itself, I think
withAsync (startServer notifications)
(const $ fancyShow notifications)
其中
notifications
是一个 IORef
包装一些状态(大致为 [a]
),该状态由 notify
(根据上面第一个片段由 export
编辑)和 startServer
突变:前者在新通知到来时将其填充到 fancyShow
的内部,后者每 1/10 秒轮询一次 IORef
并将其清空,同时负责以图形方式显示通知。现在 IORef
是使用
fancyShow
的东西,因此永远不会返回,所以我不明白为什么我应该阻止 forever
返回;毕竟,
它在分配 startServer
IORef
返回不会使该状态无效,局部变量 startServer
client
之外没有其他理由存在,这是一个 export
操作,无论 IO
是否返回,本地 vairbale startServer
reply
返回,但我想我可以始终让 startServer
返回,并准确地将 startServer
返回给调用者以通知reply
是否成功。
一旦
export
返回,接收通知并将其放入
startServer
的机制就位,而 IORef
会永远运行,完成从 fancyShow
中拉出内容的工作(并显示它)在屏幕上),同时...当通知到来时,IORef
继续完成填充notify
的工作?但是谁让IORef
活着?
notify
是否有效地将
export
(以及其他两种方法,fwiw)放在“安全的地方”,将让它们活着的责任转移到......其他东西?我很想认为答案是 “是的,我不需要阻止 notify
返回”
。另一方面,
startServer
的存在让我想知道我是否应该使用它,如果是的话,我是否应该在
DBus.Client.unexport
的调用者中使用它,因此暗示我不应该从中返回。但是回到上一手,这个通知服务器好像确实用了一个返回export
,而等待只是在
startServer
中完成。我的问题仍然是:main
将代码放在哪里运行
export
?是什么决定了它的寿命?notify
调用使用
connectSession
启动一个循环监听请求的线程。相关代码埋在forkIO
:connectWith'
其中,默认情况下,
connectWith' opts addr = do
...
threadID <- forkIO $ do
client <- readMVar clientMVar
threadRunner (mainLoop client)
...
只是
threadRunner
的另一个名称。forever
调用只是通过共享
export
修改一些数据结构:IORef
因此循环线程知道导出的接口并可以向其分派适当的请求。调用
export client path interface =
atomicModifyIORef_ (clientObjects client) $ addInterface path interface
只是撤消这些更改,因此循环线程停止分派到接口。
循环线程的工作原理是在套接字上接受消息,然后调用unexport
:
dispatch
和
mainLoop client = do
...
received <- Control.Exception.try (DBus.Socket.receive sock)
msg <- case received of
...
Right msg -> return msg
dispatch client msg
的工作原理是在上述数据结构中查找目标并分叉一个线程来运行适当的回调(例如,您的
dispatch
函数):notify
因为,在 Haskell 程序中,当主线程退出时,通过
dispatch client = go where
...
go (ReceivedMethodCall serial msg) = do
pathInfo <- readIORef (clientObjects client)
...
_ <- forkIO $ case findMethodForCall (clientInterfaces client) pathInfo msg of
Right Method { methodHandler = handler } ->
runReaderT (handler msg) client >>= sendResult
...
...
分叉的所有线程都会被终止,为了保持服务请求,您所需要做的就是防止主线程退出。只要主线程存在,循环线程就会在后台继续运行(字面意义上的
forkIO
),接受消息并forever
线程来调用您的forkIO
函数。因此,正如您所猜测的那样,可以安全地从 notify
中删除
forever
循环,直接从 startServer
中的主线程调用 startServer
(无需分叉或异步任何内容),并允许 main
返回。只要主线程继续在应用程序中执行操作而不退出,一切都应该“正常工作”,就好像有一个神奇的预言机代表您从分叉线程调用 startServer
一样。特别是,如果 notify
在循环中永远运行,那么您应该能够不需要 fancyShow
,如下所示:async