Многопоточность и gtk2hs

Я пишу код с реактивным бананом и 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 вообще никогда не открывается, и я не понимаю, почему.

Как правильно это сделать? Что мне не хватает?


person Patrick Collins    schedule 09.06.2015    source источник
comment
forever $ return () — плохая идея — вместо этого дождитесь завершения потока mainGUI. например hackage.haskell.org/package/threads- 0.5.1.3/документы/   -  person chi    schedule 09.06.2015
comment
Кроме того, вы должны использовать forkOS mainGUI. См. мое обсуждение многопоточности в gtk2hs, чтобы понять почему.   -  person Daniel Wagner    schedule 09.06.2015
comment
(Автор реактивного банана здесь.) Я думаю, что проблема не зависит от реактивного банана. Мне кажется, что вам нужно -threaded для того, чтобы threadWaitRead запускался одновременно с графическим интерфейсом GTK; хотя не совсем уверен.   -  person Heinrich Apfelmus    schedule 10.06.2015
comment
@HeinrichApfelmus Да, это довольно независимо от реактивного банана, я просто включил информацию, чтобы лучше объяснить мой вариант использования. Кроме того, это мой второй реактивно-банановый вопрос, на который вы ответили лично — спасибо за активность!   -  person Patrick Collins    schedule 26.06.2015


Ответы (2)


Основная проблема здесь в том, что в Haskell, как только main завершается, вся программа срывается. Решение состоит в том, чтобы просто оставить поток main открытым; например

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

Я также заменил forkIO на forkOS. GTK использует (ОС-)локальное состояние потока в Windows, поэтому с точки зрения защитного программирования лучше всего убедиться, что mainGUI работает в связанном потоке на тот случай, если однажды вы захотите поддерживать Windows.

person Daniel Wagner    schedule 09.06.2015

Даниэль Вагнер ответил на мой вопрос так, как он был задан, но я получил более информативную точку зрения от IRC-канала #haskell, который я опубликую здесь для дальнейшего использования.

Вместо того, чтобы прыгать через неудобные обручи разветвления потока 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

person Patrick Collins    schedule 26.06.2015