|
From: Wolfgang T. <wol...@gm...> - 2004-05-11 08:41:50
|
On 11.05.2004, at 02:51, Duncan Coutts wrote:
> I would see as the goal, a solution that allows multiple Haskell
> threads
> to be bound to the single OS thread (the GUI thread), so that all calls
> that query or modify the GUI occur in the same single OS thread.
I would recommend not to go down that route...
*) It places a high burden on the Haskell implementation - GHC's is the
only RTS where this is even remotely implementable, whereas the current
"bound threads" scheme can be implemented in a variety of different
ways. I like to consider lightweight threads as an implementation
detail (used to achieve top performance in GHC) and not as a language
feature that has to be duplicated by all future implementations.
*) Having multiple threads trigger actions in the GUI thread can be
done without any special support by the RTS (see below).
*) Executing two Haskell threads in one OS thread leads to a lot of
problems when interacting
with foreign code. Nobody seems to have succeeded in even specifying
exactly what should happen in all the strange situations that arise
here. For example, let's assume we have two Haskell threads, A and B,
running in one OS thread.
Now A calls a foreign function "foo" that takes a long time to execute.
During this time, B doesn't execute at all, right?
OK, so now after "foo" is finished, A calls another foreign function,
"bar", which immediately calls back into Haskell. So as soon as the RTS
is re-entered, B should continue to run?
But what if B calls another foreign function, which in turn calls back
to Haskell? Now if the A-callback wants to return to bar, will that be
possible? No, it'll have to wait for that other foreign call in another
thread.
It's a can of worms, we _don't_ want to go there.
*) Executing two Haskell threads in one OS thread does not solve the
problem.
Sometimes we want to make library calls A, B and C in order, and not
have another thread call D in-between; for example because the library
has some global or thread-local state which we don't want to be messed
up, or perhaps because we want A, B and C to be done right after each
other (e.g. updating different parts of one window), and D might take a
long time (show a dialog box).
> I'm interested in your thoughts on this issue, (especially alternative
> proposals) and what priority you feel it deserves.
Well...
let's assume we've imported a few things from some "foreign" GUI
library (sorry, I don't know much about either gtk or wx, so I'll try
to stay general):
> runMainLoop :: IO () -- returns only when the program is over, calls
> lots of callbacks.
> -- send a user event to the main loop (wrap the IO action in a stable
> pointer)
> postUserEvent :: IO () -> IO ()
>
> {-# NOINLINE uiThread #-}
> uiThread = unsafePerformIO $ newIORef (undefined :: ThreadId)
>
> onClick :: ... -> IO () -- just an example
> onClick blah blah blah = do tid <- myThreadId
> writeIORef uiThread tid
> handleTheEvent
>
> -- handle a user event
> onUserEvent :: IO () -> IO ()
>
> onUserEvent action = do tid <- myThreadId
> writeIORef uiThread tid
> action
>
> doInUIThread action = do tid <- myThreadId
> uiTid <- readIORef uiThread
> if tid == uiTid
> then action
> else postUserEvent action
And then we can either wrap doInUIThread around every function we
import from the UI library (slow), or we can use it manually to do
things in bigger chunks.
I think we do _not_ want the handling of one event to be preempted by
UI actions from other threads; we might be in the progress of building
a dialog box in response to a user event, we don't want a background
thread to display e.g. an error message when we have a half-finished
dialog box...
Cheers,
Wolfgang
|