diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ddc2b8fc731700efdefd1d7410490b19ad672f68..6065e4aca6688e9ba0cb0ca5acba552bcc4ced6d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,19 @@ This document contains the ``fslpy`` release history in reverse chronological
 order.
 
 
+
+3.6.0 (Under development)
+-------------------------
+
+
+Changed
+^^^^^^^
+
+
+* The :class:`.TaskThread` now allows an error handler function to be
+  specified, which is run on the :mod:`.idle` loop.
+
+
 3.5.3 (Tuesday 9th February 2021)
 ---------------------------------
 
diff --git a/fsl/utils/idle.py b/fsl/utils/idle.py
index bef726633b40f8977a3a77a429d77917f7c398b0..f8ff25349c3b3bc01465dfb9a1ebba584abf543d 100644
--- a/fsl/utils/idle.py
+++ b/fsl/utils/idle.py
@@ -759,10 +759,11 @@ class Task(object):
     """Container object which encapsulates a task that is run by a
     :class:`TaskThread`.
     """
-    def __init__(self, name, func, onFinish, args, kwargs):
+    def __init__(self, name, func, onFinish, onError, args, kwargs):
         self.name     = name
         self.func     = func
         self.onFinish = onFinish
+        self.onError  = onError
         self.args     = args
         self.kwargs   = kwargs
         self.enabled  = True
@@ -808,9 +809,16 @@ class TaskThread(threading.Thread):
 
         :arg onFinish: An optional function to be called (via :func:`idle`)
                        when the task funtion has finished. Must be provided as
-                       a keyword argument. If the ``func`` raises a
-                       :class`TaskThreadVeto` error, this function will not
-                       be called.
+                       a keyword argument, and must itself accept no arguments.
+                       If the ``func`` raises a :class`TaskThreadVeto` error,
+                       this function will not be called.
+
+        :arg onError:  An optional function to be called (via :func:`idle`)
+                       if the task funtion raises an ``Exception``. Must be
+                       provided as a keyword argument, and must itself accept
+                       the raised ``Exception`` object as a single argument.
+                       If the ``func`` raises a :class`TaskThreadVeto` error,
+                       this function will not be called.
 
         All other arguments are passed through to the task function when it is
         executed.
@@ -821,16 +829,18 @@ class TaskThread(threading.Thread):
                   results.
 
         .. warning:: Make sure that your task function is not expecting keyword
-                     arguments called ``taskName`` or ``onFinish``!
+                     arguments called ``taskName``, ``onFinish``, or
+                     ``onError``!
         """
 
         name     = kwargs.pop('taskName', None)
         onFinish = kwargs.pop('onFinish', None)
+        onError  = kwargs.pop('onError',  None)
 
         log.debug('Enqueueing task: {} [{}]'.format(
             name, getattr(func, '__name__', '<unknown>')))
 
-        t = Task(name, func, onFinish, args, kwargs)
+        t = Task(name, func, onFinish, onError, args, kwargs)
         self.__enqueued[name] = t
         self.__q.put(t)
 
@@ -951,6 +961,9 @@ class TaskThread(threading.Thread):
                     type(e).__name__,
                     str(e)),
                     exc_info=True)
+                if task.onError is not None:
+                    idle(task.onError, e)
+
             finally:
                 self.__q.task_done()
 
diff --git a/tests/test_idle.py b/tests/test_idle.py
index fdee73ac7ab3ab93a02110587b6f397524df4767..e6615319eaaefcf28b7309a56570f70f8873f991 100644
--- a/tests/test_idle.py
+++ b/tests/test_idle.py
@@ -600,6 +600,37 @@ def test_TaskThread_onFinish():
     assert onFinishCalled[0]
 
 
+def test_TaskThread_onError():
+
+    taskCalled     = [False]
+    onFinishCalled = [False]
+    onErrorCalled  = [False]
+
+    def task():
+        taskCalled[0] = True
+        raise Exception('Task error')
+
+    def onFinish():
+        onFinishCalled[0] = True
+
+    def onError(e):
+        onErrorCalled[0] = str(e)
+
+    tt = idle.TaskThread()
+    tt.start()
+
+    tt.enqueue(task, onFinish=onFinish, onError=onError)
+
+    time.sleep(0.5)
+
+    tt.stop()
+    tt.join()
+
+    assert     taskCalled[0]
+    assert     onErrorCalled[0] == 'Task error'
+    assert not onFinishCalled[0]
+
+
 def test_TaskThread_isQueued():
 
     called = [False]