diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# resource-pool-0.3.1.0 (2022-06-15)
+* Add `tryWithResource` and `tryTakeResource`.
+
 # resource-pool-0.3.0.0 (2022-06-01)
 * Rewrite based on `Control.Concurrent.QSem` for better throughput and latency.
 * Make release of resources asynchronous exceptions safe.
diff --git a/resource-pool.cabal b/resource-pool.cabal
--- a/resource-pool.cabal
+++ b/resource-pool.cabal
@@ -1,7 +1,7 @@
 cabal-version:       2.4
 build-type:          Simple
 name:                resource-pool
-version:             0.3.0.0
+version:             0.3.1.0
 license:             BSD-3-Clause
 license-file:        LICENSE
 category:            Data, Database, Network
diff --git a/src/Data/Pool.hs b/src/Data/Pool.hs
--- a/src/Data/Pool.hs
+++ b/src/Data/Pool.hs
@@ -10,6 +10,8 @@
     -- * Resource management
   , withResource
   , takeResource
+  , tryWithResource
+  , tryTakeResource
   , putResource
   , destroyResource
   , destroyAllResources
@@ -67,18 +69,30 @@
         Nothing -> do
           a <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
           pure (a, lp)
-    else case cache stripe of
-      [] -> do
-        putMVar (stripeVar lp) $! stripe { available = available stripe - 1 }
-        a <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
-        pure (a, lp)
-      Entry a _ : as -> do
-        putMVar (stripeVar lp) $! stripe
-         { available = available stripe - 1
-         , cache = as
-         }
-        pure (a, lp)
+    else takeAvailableResource pool lp stripe
 
+-- | A variant of 'withResource' that doesn't execute the action and returns
+-- 'Nothing' instead of blocking if the capability-local pool is exhausted.
+tryWithResource :: Pool a -> (a -> IO r) -> IO (Maybe r)
+tryWithResource pool act = mask $ \unmask -> tryTakeResource pool >>= \case
+  Just (res, localPool) -> do
+    r <- unmask (act res) `onException` destroyResource pool localPool res
+    putResource localPool res
+    pure (Just r)
+  Nothing -> pure Nothing
+
+-- | A variant of 'takeResource' that returns 'Nothing' instead of blocking if
+-- the capability-local pool is exhausted.
+tryTakeResource :: Pool a -> IO (Maybe (a, LocalPool a))
+tryTakeResource pool = mask_ $ do
+  lp <- getLocalPool (localPools pool)
+  stripe <- takeMVar (stripeVar lp)
+  if available stripe == 0
+    then do
+      putMVar (stripeVar lp) stripe
+      pure Nothing
+    else Just <$> takeAvailableResource pool lp stripe
+
 {-# DEPRECATED createPool "Use newPool instead" #-}
 -- | Provided for compatibility with @resource-pool < 0.3@.
 --
@@ -90,3 +104,23 @@
   , poolCacheTTL     = realToFrac idleTime
   , poolMaxResources = numStripes * maxResources
   }
+
+----------------------------------------
+-- Helpers
+
+takeAvailableResource
+  :: Pool a
+  -> LocalPool a
+  -> Stripe a
+  -> IO (a, LocalPool a)
+takeAvailableResource pool lp stripe = case cache stripe of
+  [] -> do
+    putMVar (stripeVar lp) $! stripe { available = available stripe - 1 }
+    a <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
+    pure (a, lp)
+  Entry a _ : as -> do
+    putMVar (stripeVar lp) $! stripe
+     { available = available stripe - 1
+     , cache = as
+     }
+    pure (a, lp)
diff --git a/src/Data/Pool/Introspection.hs b/src/Data/Pool/Introspection.hs
--- a/src/Data/Pool/Introspection.hs
+++ b/src/Data/Pool/Introspection.hs
@@ -11,6 +11,8 @@
   , Acquisition(..)
   , withResource
   , takeResource
+  , tryWithResource
+  , tryTakeResource
   , putResource
   , destroyResource
   , destroyAllResources
@@ -84,32 +86,66 @@
                 , creationTime       = Just $! t3 - t2
                 }
           pure (res, lp)
-    else case cache stripe of
-      [] -> do
-        let newAvailable = available stripe - 1
-        putMVar (stripeVar lp) $! stripe { available = newAvailable }
-        t2 <- getMonotonicTime
-        a  <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
-        t3 <- getMonotonicTime
-        let res = Resource
-              { resource           = a
-              , stripeNumber       = stripeId lp
-              , availableResources = newAvailable
-              , acquisition        = Immediate
-              , acquisitionTime    = t2 - t1
-              , creationTime       = Just $! t3 - t2
-              }
-        pure (res, lp)
-      Entry a _ : as -> do
-        let newAvailable = available stripe - 1
-        putMVar (stripeVar lp) $! stripe { available = newAvailable, cache = as }
-        t2 <- getMonotonicTime
-        let res = Resource
-              { resource           = a
-              , stripeNumber       = stripeId lp
-              , availableResources = newAvailable
-              , acquisition        = Immediate
-              , acquisitionTime    = t2 - t1
-              , creationTime       = Nothing
-              }
-        pure (res, lp)
+    else takeAvailableResource pool t1 lp stripe
+
+-- | A variant of 'withResource' that doesn't execute the action and returns
+-- 'Nothing' instead of blocking if the capability-local pool is exhausted.
+tryWithResource :: Pool a -> (Resource a -> IO r) -> IO (Maybe r)
+tryWithResource pool act = mask $ \unmask -> tryTakeResource pool >>= \case
+  Just (res, localPool) -> do
+    r <- unmask (act res) `onException` destroyResource pool localPool (resource res)
+    putResource localPool (resource res)
+    pure (Just r)
+  Nothing -> pure Nothing
+
+-- | A variant of 'takeResource' that returns 'Nothing' instead of blocking if
+-- the capability-local pool is exhausted.
+tryTakeResource :: Pool a -> IO (Maybe (Resource a, LocalPool a))
+tryTakeResource pool = mask_ $ do
+  t1 <- getMonotonicTime
+  lp <- getLocalPool (localPools pool)
+  stripe <- takeMVar (stripeVar lp)
+  if available stripe == 0
+    then do
+      putMVar (stripeVar lp) stripe
+      pure Nothing
+    else Just <$> takeAvailableResource pool t1 lp stripe
+
+----------------------------------------
+-- Helpers
+
+takeAvailableResource
+  :: Pool a
+  -> Double
+  -> LocalPool a
+  -> Stripe a
+  -> IO (Resource a, LocalPool a)
+takeAvailableResource pool t1 lp stripe = case cache stripe of
+  [] -> do
+    let newAvailable = available stripe - 1
+    putMVar (stripeVar lp) $! stripe { available = newAvailable }
+    t2 <- getMonotonicTime
+    a  <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
+    t3 <- getMonotonicTime
+    let res = Resource
+          { resource           = a
+          , stripeNumber       = stripeId lp
+          , availableResources = newAvailable
+          , acquisition        = Immediate
+          , acquisitionTime    = t2 - t1
+          , creationTime       = Just $! t3 - t2
+          }
+    pure (res, lp)
+  Entry a _ : as -> do
+    let newAvailable = available stripe - 1
+    putMVar (stripeVar lp) $! stripe { available = newAvailable, cache = as }
+    t2 <- getMonotonicTime
+    let res = Resource
+          { resource           = a
+          , stripeNumber       = stripeId lp
+          , availableResources = newAvailable
+          , acquisition        = Immediate
+          , acquisitionTime    = t2 - t1
+          , creationTime       = Nothing
+          }
+    pure (res, lp)
