iceiceice
iceiceice

Reputation: 187

How to exit a forkIO thread when it excutes a loop procedure

I'm writing something like a music player and get stuck with the playback progress bar.

In my program when the play button is clicked, I use forkIO to fork a thread which controls the progressbar. However, the forked thread now executes a loop. How can I inform that thread to terminate when I stop current song or change songs.

I've been trying to use IORef Var, for example

flag <- newIORef False

forkIO $ progressBarFunc flag

and in the function progreeBarFunc it checks whether flag is true and decides to exit loop or not.

But this does not work.

More generally, how can I tell the forked thread to stop when I use forkIO to fork threads?

In addition, if I have an IORef Var and pass it to the function in forkIO, do the main thread and the forked thread share the same IORef Var or the forked thread actually has a copy of it?

Upvotes: 2

Views: 279

Answers (1)

Cirdec
Cirdec

Reputation: 24156

You can communicate between threads using IORefs. The IORef refers to the same thing in the forked thread as it did in the main thread.

There are a few things you should check:

  • Does the forked thread actually get a chance to test the IORef?
  • Can the UI interactions you are expecting actually happen from the forked thread? Many UI libraries, including both gtk and OpenGL, have restrictions on which threads can interact with the UI.
  • Is the flag set for long enough that the forked thread had a chance to see it? If the flag is set to True and then back to False before the forked thread calls readIORef, it won't detect the stop.

One way to address the final problem is to use an Integer instead of a Bool for a flag.

newFlag :: IO (IORef Integer)
newFlag = newIORef 0

An observer of the flag remembers the value of the flag when the observer was created, and stops when it becomes greater. This returns True when the thread can continue (the flag has not been raised).

testFlag :: IORef Integer -> IO (IO Bool)
testFlag flag = do
    n <- readIORef flag
    return (fmap (<=n) (readIORef flag))

To raise the flag, the signaler increments the value.

raiseFlag :: IORef Integer -> IO ()
raiseFlag ref = atomicModifyIORef ref (\x -> (x+1,()))

This little example program demonstrates an IORef sharing a flag with other threads. It forks new threads when given the input "f", signals the threads to stop when given the input "s", and quits when given the input "q".

main = do
    flag <- newFlag
    let go = do
        command <- getLine
        case command of
            "f" -> do
                continue <- testFlag flag
                forkIO $ thread continue
                go
            "s" -> do
                raiseFlag flag
                go
            "q" -> do
                raiseFlag flag
                return ()
    go

The threads periodically do some "work", which takes half a second, and test for the continue condition before continuing.

thread :: IO Bool -> IO ()
thread continue = go
    where
        go = do
            me <- myThreadId
            putStrLn (show me ++ " Outputting")
            threadDelay 500000
            c <- continue
            if c then go else putStrLn (show me ++ " Stopping") >> return ()

Upvotes: 3

Related Questions