Gtk2Hs/Tutorials/ThreadedGUIs

From HaskellWiki
< Gtk2Hs‎ | Tutorials
Revision as of 21:36, 4 September 2012 by Timthelion (talk | contribs) (spelling and idioticisms.)
Jump to navigation Jump to search

I realized today that there was little accurate info on how to do multithreaded GUI in gtk2hs. It's trivial.

First thing you have to know, is that you MUST compile your program with -threaded for it to work properly. Otherwise you will get random hangs.

You can save this tutorial as a file and compile with:

ghc gtk2hs-threaded.lhs -threaded

>import Control.Concurrent

You need forkIO and forkOS to do things properly.

>import Control.Concurrent.MVar

You'll also need MVars.

>import Graphics.UI.Gtk

You'll also want to be able to send your own exit signals.

>import System.Exit

>main :: IO ()
>main = do

We use this function. Even though it's a place holder. It is the same as initGUI for now. But it's best to use the intended function to reduce the risk of deprecation errors.

> _ <- unsafeInitGUIForThreadedRTS

The first thing we do, is create an exit MVar. This MVar is going to keep our application alive until we wish to quit.

> exit <- newEmptyMVar

Now we can create a window with some buttons and some threads of our own. Watch out though, at least one button MUST be bound to filling the exit MVar, or else our program will close immediately and complain that it blocked indefinitely on an MVar operation.

> window <- windowNew

Our window will be very simple. It will show a single label, showing the time in seconds since the program started, and an exit button.

> myTopHBox <- hBoxNew False 0
> containerAdd window myTopHBox
> timeLabel <- labelNew Nothing
> boxPackStart myTopHBox timeLabel PackNatural 0

> exitButton <- buttonNewWithLabel "Exit"
> boxPackEnd myTopHBox exitButton PackNatural 0

Events are run within GTK's own thread(and GTK is single threaded). There is no need for extra precautions when running event code.

> exitButton `on` buttonActivated
>     $ putMVar exit ExitSuccess

Now we're going to make a thread that updates the time label every second.

> forkIO $ do
>  let
>    printTime t = do{

So we wait one second.

> threadDelay 1000000;

And then we want to print that time to the label. We are not in GTK's own thread however. So we'll have to send GTK a drawing action(of type IO a). We can use one of two functions to do this. postGUIAsync, which simply runs the action inside GTK's thread, or postGUISync, which runs the action and then returns the result. Obviously Async is non blocking whereas Sync is blocking. If you forget to put postGUI(A)Sync, your program may crash randomly in horrible ways. Arguably, gtk drawing actions should be typed in a GTK monad so that the compiler would prevent such unfortunate runtime errors. However no one has bothered to do this(yet), so we end up suffering a bit sometimes.

>     postGUIAsync
>      $ labelSetText timeLabel (show t);

>     printTime (t+1)}
>  printTime 0

This is the function that starts GTK's event loop in it's own thread. This must be forkOS and not forkIO as the GTK thread is a FFI thread and cannot take advantage of Haskell's "green thread" capabilities..

After having set up our window, we now need to pack it:

> widgetShowAll window

> forkOS mainGUI

When we want our program to exit, we will just fill this MVar with an exit signal.

> signal <- takeMVar exit

We rarely want to exit imediately. Usually, we want to wait for some other thread to finish saving the file/showing a dialog asking if the user wants to save. We should have appropriate waiting code here.

We also want to close GTK. Again, GTK is in another thread, so we have to post the action to the GTK thread:

> postGUIAsync mainQuit

And then we can exit.

> exitWith signal