diff --git a/fsl/utils/async.py b/fsl/utils/async.py
index 08142c82fa670e8be60236cad2c7ab3faa5d996a..938a83c384746540cf77bce76dd089e64591d4aa 100644
--- a/fsl/utils/async.py
+++ b/fsl/utils/async.py
@@ -96,14 +96,6 @@ except: import Queue as queue
 log = logging.getLogger(__name__)
 
 
-def _haveWX():
-    """Returns ``True`` if we are running within a ``wx`` application,
-    ``False`` otherwise.
-    """
-    import fsl.utils.platform as fslplatform
-    return fslplatform.platform.haveGui
-
-
 def run(task, onFinish=None, onError=None, name=None):
     """Run the given ``task`` in a separate thread.
 
@@ -125,10 +117,12 @@ def run(task, onFinish=None, onError=None, name=None):
               the return value will be ``None``.
     """
 
+    from fsl.utils.platform import platform as fslplatform
+
     if name is None:
         name = getattr(task, '__name__', '<unknown>')
 
-    haveWX = _haveWX()
+    haveWX = fslplatform.haveGui
 
     # Calls the onFinish or onError handler
     def callback(cb, *args, **kwargs):
@@ -397,9 +391,11 @@ def idle(task, *args, **kwargs):
                        argument. If ``True``, and a ``wx.MainLoop`` is not
                        running, the task is enqueued anyway, under the
                        assumption that a ``wx.MainLoop`` will be started in
-                       the future. Note that another  call to ``idle`` must
-                       be made after the ``MainLoop`` has started for the
-                       original task to be executed.
+                       the future. Note that, if ``wx.App`` has not yet been
+                       created, another  call to ``idle`` must be made after
+                       the app has been created for the original task to be
+                       executed. If ``wx`` is not available, this parameter
+                       will be ignored, and the task executed directly.
 
 
     All other arguments are passed through to the task function.
@@ -426,6 +422,8 @@ def idle(task, *args, **kwargs):
               ``alwaysQueue``.
     """
 
+    from fsl.utils.platform import platform as fslplatform
+
     global _idleRegistered
     global _idleTimer
     global _idleQueue
@@ -439,13 +437,29 @@ def idle(task, *args, **kwargs):
     skipIfQueued = kwargs.pop('skipIfQueued', False)
     alwaysQueue  = kwargs.pop('alwaysQueue',  False)
 
-    havewx       = _haveWX()
+    canHaveGui = fslplatform.canHaveGui
+    haveGui    = fslplatform.haveGui
+
+    # If there is no possibility of a
+    # gui being available in the future,
+    # then alwaysQueue is ignored.
+    if haveGui or (alwaysQueue and canHaveGui):
 
-    if havewx or alwaysQueue:
         import wx
+        app = wx.GetApp()
+
+        # Register on the idle event
+        # if an app is available
+        #
+        # n.b. The 'app is not None' test will
+        # potentially fail in scenarios where
+        # multiple wx.Apps have been instantiated,
+        # as it may return a previously created
+        # app.
+        if (not _idleRegistered) and (app is not None):
+
+            log.debug('Registering async idle loop')
 
-        if havewx and (not _idleRegistered):
-            app = wx.GetApp()
             app.Bind(wx.EVT_IDLE, _wxIdleLoop)
 
             _idleTimer      = wx.Timer(app)
@@ -546,12 +560,14 @@ def wait(threads, task, *args, **kwargs):
               a keyword argument called ``wait_direct``.
     """
 
+    from fsl.utils.platform import platform as fslplatform
+
     direct = kwargs.pop('wait_direct', False)
 
     if not isinstance(threads, collections.Sequence):
         threads = [threads]
 
-    haveWX = _haveWX()
+    haveWX = fslplatform.haveGui
 
     def joinAll():
         log.debug('Wait thread joining on all targets')
diff --git a/tests/test_async.py b/tests/test_async.py
index 9d96801c8f469f356850931bdf877ae5d5c3a4f1..9a2e03d56b0aaf4db299eaf2d694da1636ab0f14 100644
--- a/tests/test_async.py
+++ b/tests/test_async.py
@@ -9,29 +9,46 @@ import time
 import threading
 import random
 
+from six.moves import reload_module
+
 import pytest
+import mock
 
 import fsl.utils.async as async
 from fsl.utils.platform import platform as fslplatform
 
-
+# We use a single wx.App object because wx.GetApp()
+# will still return an old App objectd after its
+# mainloop has exited. and therefore async.idle
+# will potentially register on EVT_IDLE with the
+# wrong wx.App object.
+_wxapp = None
 def _run_with_wx(func, *args, **kwargs):
 
+    global _wxapp
+
     propagateRaise = kwargs.pop('propagateRaise', True)
     startingDelay  = kwargs.pop('startingDelay',  500)
     finishingDelay = kwargs.pop('finishingDelay', 500)
-    
-    import wx 
+    callAfterApp   = kwargs.pop('callAfterApp',   None)
+
+    import wx
 
     result = [None]
     raised = [None]
-    app    = wx.App()
+
+    if _wxapp is None:
+        _wxapp    = wx.App()
     frame  = wx.Frame(None)
-    
+
+    if callAfterApp is not None:
+        callAfterApp()
+
     def wrap():
 
         try:
-            result[0] = func(*args, **kwargs)
+            if func is not None:
+                result[0] = func(*args, **kwargs)
 
         except Exception as e:
             print(e)
@@ -40,14 +57,14 @@ def _run_with_wx(func, *args, **kwargs):
         finally:
             def finish():
                 frame.Destroy()
-                app.ExitMainLoop() 
+                _wxapp.ExitMainLoop()
             wx.CallLater(finishingDelay, finish)
     
     frame.Show()
 
     wx.CallLater(startingDelay, wrap)
 
-    app.MainLoop()
+    _wxapp.MainLoop()
     async.idleReset()
 
     if raised[0] and propagateRaise:
@@ -244,25 +261,98 @@ def test_idle_dropIfQueued():
     assert     task2called[0]
 
 
-def test_idle_alwaysQueue():
+def test_idle_alwaysQueue1():
 
+    # Test scheduling the task before
+    # a wx.App has been created.
     called = [False]
 
     def task():
         called[0] = True
 
+    # In this scenario, an additional call
+    # to idle (after the App has been created)
+    # is necessary, otherwise the originally
+    # queued task will not be called.
     def nop():
         pass
 
+    # The task should be run
+    # when the mainloop starts
     async.idle(task, alwaysQueue=True)
 
-    # We need to queue another task
-    # for the first task to be executed
+    # Second call to async.idle
     _run_with_wx(async.idle, nop)
 
     assert called[0]
 
 
+def test_idle_alwaysQueue2():
+
+    # Test scheduling the task
+    # after a wx.App has been craeted,
+    # but before MainLoop has started
+
+    called = [False]
+
+    def task():
+        called[0] = True
+
+    def queue():
+        async.idle(task, alwaysQueue=True)
+
+    _run_with_wx(None, callAfterApp=queue)
+
+    assert called[0]
+
+
+def test_idle_alwaysQueue3():
+
+    # Test scheduling the task
+    # after a wx.App has been craeted
+    # and the MainLoop has started.
+    # In this case, alwaysQueue should
+    # have no effect - the task should
+    # just be queued and executed as
+    # normal.
+
+    called = [False]
+
+    def task():
+        called[0] = True
+
+    _run_with_wx(async.idle, task, alwaysQueue=True)
+
+    assert called[0]
+
+
+def test_idle_alwaysQueue4():
+
+    # Test scheduling the task when
+    # wx is not present - the task
+    # should just be executed immediately
+    called = [False]
+
+    def task():
+        called[0] = True
+
+    import fsl.utils.platform
+    with mock.patch.dict('sys.modules', {'wx' : None}):
+
+        # async uses the platform module to
+        # determine whether a GUI is available,
+        # so we have to reload it
+        reload_module(fsl.utils.platform)
+        async.idle(task, alwaysQueue=True)
+
+        with pytest.raises(ImportError):
+            import wx
+
+    reload_module(fsl.utils.platform)
+
+    assert called[0]
+
+
 def test_idle_timeout():
 
     called = [False]