diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,13 @@
 # Changelog
   
+## [0.3.0.2] - 2024-10-03
+
+### Added
+
+* `withLoopers`
+* `withLoopersIgnoreOverrun`
+* `withLoopersRaw`
+  
 ## [0.3.0.1] - 2024-10-03
 
 ### Added
diff --git a/looper.cabal b/looper.cabal
--- a/looper.cabal
+++ b/looper.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.36.0.
+-- This file has been generated from package.yaml by hpack version 0.36.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           looper
-version:        0.3.0.1
+version:        0.3.0.2
 description:    Configure and run recurring jobs indefinitely
 homepage:       https://github.com/NorfairKing/looper#readme
 bug-reports:    https://github.com/NorfairKing/looper/issues
diff --git a/src/Looper.hs b/src/Looper.hs
--- a/src/Looper.hs
+++ b/src/Looper.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE NumericUnderscores #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 
 module Looper
   ( LooperDef (..),
@@ -14,17 +15,23 @@
     LooperSettings (..),
     parseLooperSettings,
     mkLooperDef,
+    withLoopers,
     runLoopers,
+    withLoopersIgnoreOverrun,
     runLoopersIgnoreOverrun,
+    withLoopersRaw,
     runLoopersRaw,
     runLooperDef,
     waitNominalDiffTime,
   )
 where
 
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NE
 import Data.Maybe
 import Data.Text (Text)
 import Data.Time
+import Data.Void
 import GHC.Generics (Generic)
 import OptEnvConf
 import UnliftIO
@@ -126,6 +133,10 @@
       looperDefFunc = func
     }
 
+-- | Like 'runLoopers' but runs the loopers as background jobs.
+withLoopers :: (MonadUnliftIO m) => [LooperDef m] -> m a -> m a
+withLoopers = withLoopersIgnoreOverrun looperDefFunc
+
 -- | Simply run loopers
 --
 -- > runLoopers = runLoopersIgnoreOverrun looperDefFunc
@@ -136,6 +147,17 @@
 runLoopers :: (MonadUnliftIO m) => [LooperDef m] -> m ()
 runLoopers = runLoopersIgnoreOverrun looperDefFunc
 
+-- | Like 'runLoopersIgnoreOverrun' but runs the loopers as background jobs.
+withLoopersIgnoreOverrun ::
+  (MonadUnliftIO n) =>
+  -- | Custom runner
+  (LooperDef m -> n ()) ->
+  -- | Loopers
+  [LooperDef m] ->
+  n a ->
+  n a
+withLoopersIgnoreOverrun = withLoopersRaw (const $ pure ())
+
 -- | Run loopers with a custom runner, ignoring any overruns
 --
 -- > runLoopersIgnoreOverrun = runLoopersRaw (pure ())
@@ -151,6 +173,28 @@
   [LooperDef m] ->
   n ()
 runLoopersIgnoreOverrun = runLoopersRaw (const $ pure ())
+
+-- | Like 'runLoopersRaw' but runs the loopers as background jobs.
+withLoopersRaw ::
+  forall m n a.
+  (MonadUnliftIO n) =>
+  -- | Overrun handler
+  (LooperDef m -> n ()) ->
+  -- | Runner
+  (LooperDef m -> n ()) ->
+  -- | Loopers
+  [LooperDef m] ->
+  --
+  n a ->
+  n a
+withLoopersRaw onOverrun runLooper defs func =
+  case NE.nonEmpty (mapMaybe (runLooperDef onOverrun runLooper) defs) of
+    Nothing -> func
+    Just looperThreads -> do
+      stopOrResult <- race (mapConcurrently id looperThreads) func
+      case stopOrResult :: Either (NonEmpty Void) a of
+        Left _ -> error "uninhabited."
+        Right result -> pure result
 
 -- | Run loopers, with a custom runner and overrun handler
 --
