Athan Clark
Athan Clark

Reputation: 3980

Canceling an Async thread from within the thread itself

Is there a way to use cancel from within a thread invoked with async from the async package? I can see that you can cancel it from outside the thread, but I'm wondering if there could be a cancelSelf :: IO () function which stops its own execution. I could staple something together with unique id generation and a shared Map of Async thread references, which the thread itself could reference, but that just seems too much. Could I get away with an uncaught exception or something?

Upvotes: 2

Views: 149

Answers (1)

Carl
Carl

Reputation: 27013

An async action can cancel itself. It involves a bit of a trick, though.

{-# LANGUAGE RecursiveDo #-}

import Control.Concurrent.Async

main :: IO ()
main = do
    rec let doCancel = cancel calculate
        calculate <- async doCancel
    wait calculate

In theory, you can do that without RecursiveDo, but I've never wanted to write an mfix expression (what RecursiveDo bindings desugar to) by hand.

RecursiveDo allows you to create a mutually-recursive set of definitions inside a do block, even if some of the definitions are bound with <- and some are defined inside a let statement. As always, if there is a real circularity involved, the calculation will diverge. But there are lots of cases where all you want to do is be able to refer to the name of something else like the example above, and RecursiveDo works just fine.

Oh, and the implementation of mfix for IO is terrifying. I'm glad I didn't have to write it myself.

-- Edit --

Since this has received almost no feedback, I've realized it's not completely obvious how to use this in order to solve your problem. So here's an expanded example that uses a combinator to spawn an Async that can cancel itself:

{-# LANGUAGE RecursiveDo #-}

-- obviously want the async library
import Control.Concurrent.Async

-- used in selfCancelableAsync
import Control.Monad      (forever)
import Control.Concurrent (threadDelay)

-- only used for demonstration
import System.Random      (randomIO)

main :: IO ()
main = do
    a <- selfCancelableAsync $ \selfCancel -> do
        choice <- randomIO
        if choice then return "Success!" else selfCancel
    result <- wait a
    putStrLn result

-- spawns an Async that has the ability to cancel itself by
-- using the action passed to the IO action it's running
selfCancelableAsync :: (IO a -> IO b) -> IO (Async b)
selfCancelableAsync withCancel = do
    rec let doCancel = do
                cancel calculate
                -- This must never return to ensure it has the correct type.
                -- It uses threadDelay to ensure it's not hogging resources
                -- if it takes a moment to get killed.
                forever $ threadDelay 1000

        calculate <- async $ withCancel doCancel

    return calculate

Upvotes: 5

Related Questions