3

我正在使用 WxHaskell 以图形方式显示使用 TCP(我使用 Data.Binary 解码)通告状态更新的程序的状态。收到更新后,我想更新显示。所以我希望 GUI 异步更新它的显示。我知道processExecAsync异步运行命令行进程,但我认为这不是我想要的。

4

3 回答 3

2

这是使用事务变量(即软件事务内存)的粗略代码。您可以使用 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 占用的轮询循环。

在这段代码中,我假设您有名为updateGUICounterinitGUI和的函数initNetwork。我建议您使用 Hoogle 查找您还不知道的任何其他功能的位置,并了解每个模块的一些知识。

于 2010-07-05T18:32:24.147 回答
1

我想出了一种似乎可行的技巧。即,使用事件计时器检查更新队列:

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 毫秒,会引发一个事件,其操作是检查此队列并在静态文本小部件中显示最新更新。

如果您有更好的解决方案,请告诉我!

于 2010-07-05T23:03:08.237 回答
0

我在没有繁忙等待的情况下找到了解决方案: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线程中运行的。

于 2012-09-26T07:22:46.187 回答