Is it safe to trigger the fire action in
(addHandler, fire) <- newAddHandler
from a different thread from which the reactive-banana graph was compiled?
Yes, this is safe, but there is the caveat that @Cirdec mentioned.
For conreteness, consider the following example that creates an event network using the addHandler
in a separate thread and then calls fire
repeatedly in the main thread
import Control.Concurrent (myThreadId, threadDelay, forkIO)
main = do
...
(addHandler, fire) <- newAddHandler
let networkDescription :: MomentIO ()
networkDescription = do
e <- fromAddHandler addHandler
...
reactimate $ (print =<< myThreadId) <$ e -- reactimate
forkIO $ do
network <- compile networkDescription
actuate network
...
forever $ do -- event loop
threadDelay (10^6)
fire ()
(See the documentation "Terminating the program" in Control.Concurrent for why I've put the event loop in the main thread as opposed to putting the network in the main thread.)
In this and similar situations, the following will hold:
reactimate
will be run in the thread that calls fire
, not in the thread where the network was compiled. This is what @Cirdec already mentioned.fire
, then it could potentially interleave with other calls to fire
, i.e. the program could be calling fire
twice concurrently. Then,
Time -> a
and lists [(Time,a)]
as usual.reactimate
s may interleave. In other words, the pure FRP part will stay pure, but the actual IO is subject to concurrency as usual.Firing the fire
handler itself is safe; it reads an IORef
that is being updated atomically and runs each of the added handlers in the current thread. Whether or not that's safe will depend on what handlers have been added to the addHandler
.
Using the addHandler
in interpretAsHandler
, fromAddHandler
, or fromChanges
should be safe. Nothing I know of in reactive-banana has any thread affinity, and even if it did, these are what newAddHandler
was made for, so it should be safe anyway.
What you need to be careful of is the IO ()
actions executed by reactimate
. If you need to reactimate IO
actions that need to be run in a specific thread (for OpenGL output, etc), you need to only produce IO ()
actions that will send their data to that thread. In this complete OpenGL example for reactive-banana the IO ()
actions for OpenGL output, which have thread affinity, are run in the OpenGL thread. Instead ofreactimate
ing the Event (IO ())
executing them directly they are added to an IORef
whenIdleRef <- newIORef (return ())
let
addWhenIdle :: IO () -> IO ()
addWhenIdle y = atomicModifyIORef' whenIdleRef (\x -> (x >> y, ()))
runWhenIdle :: IO ()
runWhenIdle = atomicModifyIORef' whenIdleRef (\x -> (return (), x)) >>= id
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
reactimate $ fmap addWhenIdle (whenIdle outputs)
^ ^
| Event (IO ())
Stuff the event into an IORef
The IORef
holding which IO ()
actions to run is read and each of all the actions are run in a context that I know is in the OpenGL thread.
idleCallback $= Just (do -- will be executed in the OpenGL thread when it's idle
getCurrentTime >>= raiseTime
runWhenIdle -- run those `IO ()` actions in this thread
postRedisplay Nothing)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With