2

我正在用反应香蕉和 gtk2hs 编写一些需要从文件句柄中读取的代码。我需要至少有两个线程(一个用反应香蕉读取键盘事件,一个从文件句柄读取),所以目前我的代码看起来像这样:

type EventSource a = (AddHandler a, a -> IO ())

fire :: EventSource a -> a -> IO ()
fire = snd

watch :: EventSource ByteString -> Handle -> IO ()
watch textIn pty = forever $
  hGetLine pty >>= fire textIn >> threadWaitRead pty

具有以下主要功能:

mainAxn :: IO ()
mainAxn = do
  h <- openFile "foo" ReadMode

  initGUI

  win <- windowNew
  txt <- textViewNew

  containerAdd win txt

  widgetShowAll win

  (keyPress, textIn) <-
    (,) <$> newAddHandler <*> newAddHandler
  network <- setupNetwork keyPress textIn
  actuate network

  _ <- forkIO $ watch textIn h

  _ <- win `on` keyPressEvent $
       eventKeyVal >>= liftIO . fire keyPress >> return True

  mainGUI

我的活动网络设置如下:

setupNetwork :: EventSource KeyVal -> EventSource ByteString -> IO EventNetwork
setupNetwork keyPress textIn = compile $ do
  ePressed <- fromAddHandler $ addHandler keyPress
  eText <- fromAddHandler $ addHandler textIn

  reactimate $ print <$> (filterJust $ keyToChar <$> ePressed)
  reactimate $ print <$> eText

(除了在我的实际代码中,这些reactimate调用写入TextView内置mainAxn)。我发现我需要构建-threaded以使事件网络正确捕获来自的文本和来自的textIn按键keyPress,这会导致问题,因为同时修改 gtk 包中的对象是不安全的。

目前,postGUIAsync我的代码中分散了调用,我发现 usingpostGUISync会导致整个事情陷入僵局——我不知道为什么。我认为这是因为我最终postGUISync在运行的同一个线程内部调用mainGUI

似乎最好在自己的线程中运行所有 GUI 内容并使用这些postGUI*函数进行每次访问。但是,当我将最后一行更改mainAxn

forkIO mainGUI
return ()

程序到达末尾时立即返回mainAxn。我试图通过使用来解决这个问题:

forkIO mainGUI 
forever $ return ()

但是 gtk GUI 根本没有打开,我不明白为什么。

这样做的正确方法是什么?我错过了什么?

4

2 回答 2

3

这里的基本问题是,在 Haskell 中,一旦main退出,整个程序就会被拆除。解决方案就是保持main线程打开;例如

done <- newEmptyMVar
forkOS (mainGUI >> putMVar done ())
takeMVar done

我也换成forkIOforkOS. GTK 在 Windows 上使用(OS-)线程本地状态,因此作为防御性编程问题,最好确保mainGUI在绑定线程上运行,以防万一有一天您想要支持 Windows。

于 2015-06-09T19:05:14.753 回答
1

Daniel Wagner 回答了我的问题,但我从#haskell IRC 频道获得了更多信息,我将在此处发布以供将来参考。

与其跳过分叉 GUI 线程和让主线程进入睡眠的尴尬循环,更好的解决方案是让主线程成为 GUI 线程并在新线程中处理反应香蕉事件网络。我最终修改了我的main函数以包含以下内容:

keyChan <- newChan
_ <- forkIO $ watchKeys keyPress keyChan
_ <- win `on` keyPressEvent $
    eventKeyVal >>= liftIO . writeChan keyChan >> return True

其中watchKeys定义为:

watchKeys :: EventSource KeyVal -> Chan KeyVal -> IO ()
watchKeys keyPress chan = forever $
    readChan chan >>= fire keyPress 

postGUI(A)Sync现在我可以在一个地方处理这些问题,方法是定义:

reactimateSafe :: Frameworks t => Event t (IO ()) -> Moment t ()
reactimateSafe = reactimate . fmap postGUIAsync

reactimateSafe用于修改 GTK 对象的任何 IO 操作

于 2015-06-26T12:56:38.593 回答