我正在使用 WxHaskell 以图形方式显示使用 TCP(我使用 Data.Binary 解码)通告状态更新的程序的状态。收到更新后,我想更新显示。所以我希望 GUI 异步更新它的显示。我知道processExecAsync
异步运行命令行进程,但我认为这不是我想要的。
3 回答
这是使用事务变量(即软件事务内存)的粗略代码。您可以使用 IORef、MVar 或许多其他构造。
main = do
recvFunc <- initNetwork
cntTV <- newTVarIO 0
forkIO $ threadA recvFunc cntTV
runGUI cntTV 0
上面你启动程序,初始化网络和一个共享变量cntTV
threadA recvCntFromNetwork cntTVar = forever $ do
cnt <- recvCntFromNetwork
atomically (writeTVar cntTVar cnt)
threadA
从网络接收数据并将计数器的新值写入共享变量。
runGUI cntTVar currentCnt = do
counter <- initGUI
cnt <- atomically $ do
cnt <- readTVar cntTVar
if (cnt == currentCnt)
then retry
else return cnt
updateGUICounter counter cnt
runGUI cntTVar cnt
runGUI
读取共享变量,如果有变化将更新 GUI 计数器。retry
仅供参考,runGUI 线程在被修改之前不会唤醒cntTVar
,所以这不是 CPU 占用的轮询循环。
在这段代码中,我假设您有名为updateGUICounter
、initGUI
和的函数initNetwork
。我建议您使用 Hoogle 查找您还不知道的任何其他功能的位置,并了解每个模块的一些知识。
我想出了一种似乎可行的技巧。即,使用事件计时器检查更新队列:
startClient :: IO (TVar [Update])
startClient = /*Connect to server,
listen for updates and add to queue*/
gui :: TVar [Update] -> IO ()
gui trdl = do
f <- frame [text := "counter", visible := False]
p <- panel f []
st <- staticText p []
t <- timer f [interval := 10, on command := updateGui st]
set f [layout := container p $ fill $ widget st, clientSize := (sz 200 100), visible := True]
where
updateGui st = do
rdl <- atomically $ readTVar trdl
atomically $ writeTVar trdl []
case rdl of
[] -> return ()
dat : dl -> set st [text := (show dat)]
main :: IO ()
main = startClient >>= start gui
因此,客户端侦听 TCP 连接上的更新,并将它们添加到队列中。每 10 毫秒,会引发一个事件,其操作是检查此队列并在静态文本小部件中显示最新更新。
如果您有更好的解决方案,请告诉我!
我在没有繁忙等待的情况下找到了解决方案:http: //snipplr.com/view/17538/
但是,您可以选择更高的 eventId 以避免与现有 ID 冲突。
这是我的模块http://code.haskell.org/alsa/gui/src/Common.hs中的一些代码:
myEventId :: Int
myEventId = WXCore.wxID_HIGHEST+100
-- the custom event ID, avoid clash with Graphics.UI.WXCore.Types.varTopId
-- | the custom event is registered as a menu event
createMyEvent :: IO (WXCore.CommandEvent ())
createMyEvent =
WXCore.commandEventCreate WXCore.wxEVT_COMMAND_MENU_SELECTED myEventId
registerMyEvent :: WXCore.EvtHandler a -> IO () -> IO ()
registerMyEvent win io =
WXCore.evtHandlerOnMenuCommand win myEventId io
reactOnEvent, reactOnEventTimer ::
SndSeq.AllowInput mode =>
Int -> WX.Window a -> Sequencer mode ->
(Event.T -> IO ()) ->
IO ()
reactOnEvent _interval frame (Sequencer h _) action = do
mvar <- MVar.newEmptyMVar
void $ forkIO $ forever $ do
MVar.putMVar mvar =<< Event.input h
WXCore.evtHandlerAddPendingEvent frame =<< createMyEvent
registerMyEvent frame $
MVar.takeMVar mvar >>= action
-- naive implementation using a timer, requires Non-Blocking sequencer mode
reactOnEventTimer interval frame sequ action =
void $
WX.timer frame [
WX.interval := interval,
on command := getWaitingEvents sequ >>= mapM_ action]
该代码显示了两种处理问题的方法:
reactOnEventTimer
使用 WX 计时器进行忙碌等待。reactOnEvent
仅在事件实际到达时才会激活。这是首选的解决方案。
在我的示例中,我等待 ALSA MIDI 音序器消息。Event.input 调用等待下一条 ALSA 消息到来。获取Event.input的action
结果,也就是传入的ALSA消息,但是它是在WX线程中运行的。