The async Library
async handles exceptions and cancellation correctly. Prefer it over raw forkIO for structured concurrency.
import Control.Concurrent.Async
import Control.Concurrent (threadDelay)
import System.Timeout (timeout)
fetch :: String -> IO String
fetch url = threadDelay 500000 >> pure ("content of " ++ url)
main :: IO ()
main = do
-- Run two actions concurrently and wait for both
(a, b) <- concurrently (fetch "a.com") (fetch "b.com")
print (a, b)
-- Map concurrently over a list
pages <- mapConcurrently fetch ["x.com", "y.com", "z.com"]
print (length pages)
-- Race: the first to finish wins, the other is cancelled
r <- race (threadDelay 1000000 >> pure "slow") (fetch "fast.com")
print r -- Right "content of fast.com"
-- Explicit async handles
h <- async (fetch "later.com")
-- ... do other work ...
result <- wait h -- rethrows exceptions from the thread
print result
-- Safe scoping: the child is cancelled if the parent exits early
withAsync (fetch "scoped.com") $ \h2 -> do
r2 <- waitCatch h2 -- Either SomeException String
print r2
-- Bounded concurrency: pooledMapConcurrentlyN (from unliftio) or a semaphore (QSem)
-- Timeouts
t <- timeout 100000 (fetch
…