Personal tools

Gtk2Hs/Tutorials/ThreadedGUIs

From HaskellWiki

< Gtk2Hs | Tutorials(Difference between revisions)
Jump to: navigation, search
(spelling and idioticisms.)
 
(2 intermediate revisions by one user not shown)
Line 1: Line 1:
 
I realized today that there was little accurate info on how to do multithreaded GUI in gtk2hs. It's trivial.
 
I realized today that there was little accurate info on how to do multithreaded GUI in gtk2hs. It's trivial.
  +
  +
This is a very basic tutorial. More in depth info is [http://dmwit.com/gtk2hs/| here]
   
 
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.
 
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.
Line 7: Line 9:
 
ghc gtk2hs-threaded.lhs -threaded
 
ghc gtk2hs-threaded.lhs -threaded
   
<hask>>import Control.Concurrent</hask>
+
<haskell>>import Control.Concurrent</haskell>
   
 
You need forkIO and forkOS to do things properly.
 
You need forkIO and forkOS to do things properly.
   
<hask>>import Control.Concurrent.MVar</hask>
+
<haskell>>import Control.Concurrent.MVar</haskell>
   
 
You'll also need MVars.
 
You'll also need MVars.
   
<hask>>import Graphics.UI.Gtk</hask>
+
<haskell>>import Graphics.UI.Gtk</haskell>
   
 
You'll also want to be able to send your own exit signals.
 
You'll also want to be able to send your own exit signals.
   
<hask>>import System.Exit</hask>
+
<haskell>>import System.Exit</haskell>
   
 
<haskell>>main :: IO ()
 
<haskell>>main :: IO ()
 
>main = do</haskell>
 
>main = do</haskell>
   
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.
+
<haskell>> _ <- initGUI</haskell>
 
<hask>> _ <- unsafeInitGUIForThreadedRTS</hask>
 
   
 
The first thing we do, is create an exit MVar. This MVar is going to keep our application alive until we wish to quit.
 
The first thing we do, is create an exit MVar. This MVar is going to keep our application alive until we wish to quit.
   
<hask>> exit <- newEmptyMVar</hask>
+
<haskell>> exit <- newEmptyMVar</haskell>
   
 
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.
 
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.
   
<hask>> window <- windowNew</hask>
+
<haskell>> window <- windowNew</haskell>
   
 
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.
 
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.
Line 45: Line 47:
 
> boxPackEnd myTopHBox exitButton PackNatural 0</haskell>
 
> boxPackEnd myTopHBox exitButton PackNatural 0</haskell>
   
Events are run within GTK's own thread(and GTK is single threaded). There is no need for extra precautions when running event code.
+
Events are run within GTK's own thread. There is no need for extra precautions when running event code.
   
 
<haskell>> exitButton `on` buttonActivated
 
<haskell>> exitButton `on` buttonActivated
Line 58: Line 60:
 
So we wait one second.
 
So we wait one second.
   
<hask>> threadDelay 1000000;</hask>
+
<haskell>> threadDelay 1000000;</haskell>
   
 
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.
 
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.
Line 67: Line 69:
 
> printTime (t+1)}
 
> printTime (t+1)}
 
> printTime 0</haskell>
 
> printTime 0</haskell>
 
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:
 
After having set up our window, we now need to pack it:
   
<haskell>> widgetShowAll window
+
<haskell>> widgetShowAll window</haskell>
  +
  +
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..
   
> forkOS mainGUI</haskell>
+
<haskell>> forkOS mainGUI</haskell>
   
 
When we want our program to exit, we will just fill this MVar with an exit signal.
 
When we want our program to exit, we will just fill this MVar with an exit signal.
   
<hask>> signal <- takeMVar exit</hask>
+
<haskell>> signal <- takeMVar exit</haskell>
   
 
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 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.
Line 84: Line 84:
 
We also want to close GTK. Again, GTK is in another thread, so we have to post the action to the GTK thread:
 
We also want to close GTK. Again, GTK is in another thread, so we have to post the action to the GTK thread:
   
<hask>> postGUIAsync mainQuit</hask>
+
<haskell>> postGUIAsync mainQuit</haskell>
   
 
And then we can exit.
 
And then we can exit.
   
<hask>> exitWith signal</hask>
+
<haskell>> exitWith signal</haskell>

Latest revision as of 22:50, 4 September 2012

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

This is a very basic tutorial. More in depth info is here

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
> _ <- initGUI

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. 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

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

> widgetShowAll window

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..

> 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