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 
…