多线程和 gtk2hs

Multithreading and gtk2hs

我正在用 reactive-banana 和 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 调用写入 mainAxn 内置的 TextView)。我发现我需要使用 -threaded 进行构建以使事件网络正确捕获来自 textIn 的文本和来自 keyPress 的按键,这导致了问题,因为从 gtk 修改对象是不安全的同时打包。

目前,我有 postGUIAsync 个调用分散在我的代码中,我发现使用 postGUISync 会导致整个事情陷入僵局 --- 我不确定为什么。我认为这是因为我最终在 运行 mainGUI.

的同一线程中调用了 postGUISync

似乎最好 运行 所有 GUI 的东西都在它自己的线程中,并在每次访问它时使用 postGUI* 函数。但是,当我将 mainAxn 的最后一行更改为

forkIO mainGUI
return ()

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

forkIO mainGUI 
forever $ return ()

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

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

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

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

我还用 forkOS 替换了 forkIO。 GTK 在 Windows 上使用 (OS-) 线程局部状态,因此作为防御性编程,最好确保 mainGUI 在绑定线程上运行,以防万一有一天你想支持Windows.

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

与其跳过 GUI 线程分叉并让主线程休眠的尴尬环节,更好的解决方案是让主线程成为 GUI 线程,并在一个新的环境中处理 reactive-banana 事件网络线。我最终修改了 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

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