[Haskell-cafe] what GUI library should i select?

Sebastian Sylvan sylvan at student.chalmers.se
Tue Nov 14 06:11:12 EST 2006

On 11/14/06, Dmitry V'yal <akamaus at gmail.com> wrote:
> Bulat Ziganshin wrote:
> > it is what i say about. threaded RTS + multipls threads that does
> > computations + one thread that interfaces with Gtk2Hs. afaiu, the only
> > problem is that i need to manage both Gtk events and periodically
> > check queue of commands from other threads, but using timer + Chan
> > should allow to implement this
> >
> I wrote multi threaded gui application in Haskell last spring as a educational
> project. That was a tool for analyzing and extracting contents of FAT volumes.
>         Then i started my project, i was completely unaware of these threading issues
> and freely called gtk2hs routines from several threads. All worked fine at
> first, but when i added some disk I/O to my threads, my program became unstable.
> While on linux it just crashed from time to time, on windows i saw more curious
> behavior. At random spots threads lost ability to call WinAPI functions.
> Basically they got an error code meaning something weird like "insufficient
> memory to complete call" in return. That was not easily reproducible and
> depended on exact version of Windows. At some point my app was fully functional
> on WinXP and didn't even start on Win2000.
>         I spent a lot of time trying to debug these issues and finally refactored my
> program to make use of approach you describe.  Worked fine after that. All you
> have to do is to choose appropriate timer interval, otherwise your app becomes
> too jerky or CPU hungry.

[I sent a message earlier to this effect but accidentally hit "reply"
rather than "reply all" so only duncan got it]

I'm looking through the GTK docs and it appears to me that you don't
actually have to choose an interval at all. All that's needed is that
you somehow "trigger" the GTK event loop to call an event handler in
its thread which empties the channel and executes any GUI actions
It seems that GTK supports adding custom sources to the event loop (if
I understand the docs correctly, this is thread safe, and signalling
one of these sources should be too). So either we add a new custom
source, or find some other way of "signalling" a particular event. I
would guess one way of doing this would be to add a timeout event
handler with delay 0, this event handler would be a function called,
say, dispatchPendingGUIActions, which empties the channel of GUI
actions, and also returns False so that it will only get called once
(per "signal" - the next "signal" it will get added again).

In other words, there is no need for polling. Just add the function to
the channel and signal the main loop (first checking if we are already
in the main GUI thread in which case we just call the action directly
without going through the channel, and also checking if a "signal" has
already been raised but not yet "handled" in which case we don't do it
again) .

Something like (untested):

-- contains the action, and the response mvar
data GUIWorkItem = forall a . GUIWorkItem  !(IO a) !(MVar a)

newGUIChan :: IO (Chan GUIWorkItem)
newGUIChan = newChan

-- this adds an action to the main channel, unless we're already
-- in the main GUI thread, in which case we just run the action directly
dispatchGUIAction act
  do isMain <- isMainGUIThread
       if isMain
          then act
          else do res <- newEmptyMVar
                         writeChan theGlobalGUIChan (GUIWorkItem act res)
                         unsafeInterleaveIO (takeMVar res)

-- this is the action we want to call from the main GUI thread
-- every time a new action has been added to the queue, e.g.
-- by adding it as an event handler to a timer event
 do empty <- isEmptyChan

       if empty
         then setGTKThreadNoLongerTriggered >> return False
         else do GUIWorkItem act resVar <- readChan theGlobalGUIChan
                        res <- act
                        putMVar resVar res

-- an example GUI action, this is how we "wrap" all our actions
gtkAction' x y z = dispatchGUIAction (gtkAction x y z)

-- this is how we signal GTK so that it will call the code which
-- handles all the actions in theGlobalGUIChan
triggerMainGUIThread =
  do isTriggered <- isGTKThreadTriggered -- not necessarily needed...
        if isTriggered
           then return ()
           else timeoutAddFull dispatchPendingGUIActions priorityHigh 0

(note: The unsafeInterleaveIO is there so that
dispatchPendingGUIActions won't have to block until the main GUI
thread gets around to handling the event, after all for quite a few
GTK actions you don't really care about the result so you should
return immediately).

There are some minor gaps here, but that's the general idea. We avoid
the polling by explicitly signalling GTK whenever we add a GUI event
to the channel. I've done it here by adding the "dispatch" function as
an event handler to a timeout event (this event handler returns False
so the timeout event is removed after being called). This depends on
wether or not adding event handlers is thread safe. If it isn't, it
may be enough to wrap the triggerMainGUIThead action in a global lock
(someone who knows more obout this?). If that still isn't safe, then
we could add our own custom source to the GTK event loop and signal
the event loop using that (though that might be a bit more messy?).

Sebastian Sylvan
UIN: 44640862

More information about the Haskell-Cafe mailing list