From 4474847053aacda043ccabd0ac4d8457bf758e75 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 12 May 2016 15:40:20 +0100
Subject: [PATCH] async.run accepts a callback function to be called if the
 task raises an error.

---
 fsl/utils/async.py | 57 ++++++++++++++++++++++++++++------------------
 1 file changed, 35 insertions(+), 22 deletions(-)

diff --git a/fsl/utils/async.py b/fsl/utils/async.py
index d42fd6320..69bd30235 100644
--- a/fsl/utils/async.py
+++ b/fsl/utils/async.py
@@ -56,13 +56,17 @@ def _haveWX():
         return False
 
 
-def run(task, onFinish=None, name=None):
+def run(task, onFinish=None, onError=None, name=None):
     """Run the given ``task`` in a separate thread.
 
     :arg task:     The function to run. Must accept no arguments.
 
-    :arg onFinish: An optional function to schedule on the ``wx.MainLoop``
-                   once the ``task`` has finished. 
+    :arg onFinish: An optional function to schedule (on the ``wx.MainLoop``,
+                   via :func:`idle`) once the ``task`` has finished.
+
+    :arg onError:  An optional function to be called (on the ``wx.MainLoop``,
+                   via :func:`idle`) if the ``task`` raises an error. Passed
+                   the ``Exception`` that was raised.
 
     :arg name:     An optional name to use for this task in log statements.
 
@@ -78,33 +82,42 @@ def run(task, onFinish=None, name=None):
 
     haveWX = _haveWX()
 
-    def wrapper():
-
-        log.debug('Running task "{}"...'.format(name))
-        task()
-
-        log.debug('Task "{}" finished'.format(name))
-
-        if (onFinish is not None):
-
-            import wx
+    # Calls the onFinish or onError handler
+    def callback(cb, *args, **kwargs):
+        
+        if cb is None:
+            return
+        
+        if haveWX: idle(cb, *args, **kwargs)
+        else:      cb(      *args, **kwargs)
 
-            log.debug('Scheduling task "{}" finish handler '
-                      'on wx.MainLoop'.format(name))
+    # Runs the task, and calls 
+    # callback functions as needed.
+    def wrapper():
 
-            # Should I use the idle function here?
-            wx.CallAfter(onFinish)
+        try:
+            task()
+            log.debug('Task "{}" finished'.format(name))
+            callback(onFinish) 
+            
+        except Exception as e:
+            
+            log.warn('Task "{}" crashed', exc_info=True)
+            callback(onError, e)
 
+    # If WX, run on a thread
     if haveWX:
+        
+        log.debug('Running task "{}" on thread'.format(name))
+
         thread = threading.Thread(target=wrapper)
         thread.start()
         return thread
- 
+
+    # Otherwise run directly
     else:
-        log.debug('Running task "{}" directly'.format(name)) 
-        task()
-        log.debug('Running task "{}" finish handler'.format(name)) 
-        onFinish()
+        log.debug('Running task "{}" directly'.format(name))
+        wrapper()
         return None
 
 
-- 
GitLab