我有一个等待时间的计时器功能和一个开始时间,并在结束时返回当前的UTCTime:
runTimer :: NominalDiffTime -> UTCTime -> IO UTCTime
我想在一个单独的线程中运行这个计时器功能,因为我想同时能够捕获用户的输入。在这个主循环中,我还使用WriterT monad转换器来记录事件。我也想在计时器正常结束时记录一个事件,所以我尝试使用forkFinally:
type Log = [Entry]
data Entry = Entry {
_etype :: EntryType
, _etime :: UTCTime
} deriving Show
data EntryType = Start | End | Pause | Quit deriving Show
type Program = WriterT Log IO
loop :: Maybe ThreadId -> Program ()
loop timerId = do
liftIO $ putStr ">"
x <- liftIO $ getChar
now <- liftIO $ getCurrentTime
case x of
'q' -> do
liftIO $ putStrLn "\n Quitting..."
tell [Entry Quit now]
-- Kill the timer thread if there is one.
maybe (return ()) (liftIO . killThread) timerId
liftIO $ putStrLn "\n Quit."
's' -> case timerId of
Just _ -> do
liftIO $ putStrLn "Timer already started!"
loop timerId
Nothing -> do
-- Start a timer in a new thread.
tell [Entry Start now]
timerId' <- liftIO $ forkFinally (runTimer 5 now) eHandler
-- Call the main loop with the timer thread id.
loop (Just timerId')
_ -> do
liftIO $ putChar x
loop timerId
eHandler :: (Show a, Exception e) => Either e a -> IO ()
eHandler (Left x) = throw x
eHandler (Right x) = do
putStrLn $ show x
eHandler' :: (Exception t) => Either t UTCTime -> WriterT Log IO ()
eHandler' (Left x) = throw x
eHandler' (Right t) = do
tell [Entry End t]
当使用forkFinally和eHandler时,此代码可以正常工作。但是我如何让它与eHandler'一起使用,以便我可以在计时器结束时进行记录?
答案 0 :(得分:1)
您需要将WriterT monad转换为IO monad,如下所示:
wrapWriterT::WriterT a IO c->IO c
wrapWriterT writer = do
(result, log) <- runWriterT writer
<do something with the log, like send it to a file>
return result
然后像这样使用它:
timerId' <- liftIO $ forkFinally (runTimer 5 now) (wrapWriterT . eHandler')
这里发生了什么?请记住,Writer
monad用于传递额外的字符串,可以附加到日志中。 IO
monad用于...... IO
。 forkFinally
需要输入IO()
,但WriterT
的IO
也会返回日志。 forkFinally
不知道如何处理这个日志,但是如果你将它包装在一个函数中来提取日志并对它做一些事情,那么你只剩下一个IO
monad,{ {1}}知道如何处理。
我确实想问一下...... forkFinally
中的标准计时器函数是非阻塞的(你把它交给IO()类型的函数,然后返回,理解它将在以后完成)。你在做什么需要明确的线程?