多线程和gtk2hs

时间:2015-06-09 16:09:20

标签: multithreading haskell gtk reactive-banana

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

目前,我的代码中散布了postGUIAsync个调用,我发现使用postGUISync会导致整个事情陷入僵局 - 我不确定原因。我认为这是因为我最终在运行postGUISync的同一个线程内调用了mainGUI

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

forkIO mainGUI
return ()

程序在mainAxn结束时立即返回。我试图通过使用:

来解决这个问题
forkIO mainGUI 
forever $ return ()

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

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

2 个答案:

答案 0 :(得分:3)

这里的基本问题是,在Haskell中,只要main退出,整个程序就会被拆除。解决方案只是保持main线程打开; e.g。

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

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

答案 1 :(得分: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操作