diff --git a/fsl/utils/idle.py b/fsl/utils/idle.py
index c1184a31a758b685b476e843140a14ce0b284980..7c4c881063cd2b5c2611d1aac12e2c4335208225 100644
--- a/fsl/utils/idle.py
+++ b/fsl/utils/idle.py
@@ -391,27 +391,41 @@ def cancelIdle(taskName):
     _idleQueueDict[taskName].timeout = -1
 
 
-def block(secs, delta=0.01):
+def block(secs, delta=0.01, until=None):
     """Blocks for the specified number of seconds, yielding to the main ``wx``
     loop.
 
     If ``wx`` is not available, or a ``wx`` application is not running, this
     function is equivalent to ``time.sleep(secs)``.
 
+    If ``until`` is provided, this function will block until ``until``
+    returns ``True``, or ``secs`` have elapsed, whichever comes first.
+
     :arg secs:  Time in seconds to block
     :arg delta: Time in seconds to sleep between successive yields to ``wx``.
+    :arg until: Function which returns ``True`` or ``False``, and which
+                determins when calls to ``block`` will return.
     """
 
-    from fsl.utils.platform import platform as fslplatform
+    def defaultUntil():
+        return False
 
-    if not fslplatform.haveGui:
-        time.sleep(secs)
-    else:
-        import wx
-        start = time.time()
-        while (time.time() - start) < secs:
+    def tick():
+        if fslplatform.haveGui:
+            import wx
             wx.YieldIfNeeded()
-            time.sleep(delta)
+        time.sleep(delta)
+
+    if until is None:
+        until = defaultUntil
+
+    from fsl.utils.platform import platform as fslplatform
+
+    start = time.time()
+    while (time.time() - start) < secs:
+        tick()
+        if until():
+            break
 
 
 def idle(task, *args, **kwargs):
diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py
index 04ffb98d6b0aafcdd9c2ce6ebd62301b2c091ebd..6d3d31eda42c6f6ae70130bd558c07e9eafed985 100644
--- a/fsl/utils/platform.py
+++ b/fsl/utils/platform.py
@@ -163,9 +163,27 @@ class Platform(notifier.Notifier):
         try:
             import wx
             app = wx.GetApp()
+
+            # TODO Previously this conditional
+            #      also used app.IsMainLoopRunning()
+            #      to check that the wx main loop
+            #      was running. But this doesn't
+            #      suit situations where a non-main
+            #      event loop is running (e.g. when
+            #      the event loop is being run by
+            #      IPython).
+            #
+            #      In c++ wx, there is the
+            #      wx.App.UsesEventLoop method, but
+            #      this is not presently exposed to
+            #      Python code.
+            #
+            #      So this constraint has been
+            #      (hopefully) temporarily relaxed
+            #      until UsesEventLoop can be called
+            #      from Python.
             return (self.canHaveGui and
-                    app is not None and
-                    app.IsMainLoopRunning())
+                    app is not None)
 
         except ImportError:
             return False
diff --git a/tests/test_idle.py b/tests/test_idle.py
index 84a11b968882cac97ecfec08fcc19406dc2cdf0c..90c28c709c7d681aabb1a811743a9cbebead1498 100644
--- a/tests/test_idle.py
+++ b/tests/test_idle.py
@@ -184,6 +184,25 @@ def _test_block():
         assert called[0]
 
 
+@pytest.mark.wxtest
+def test_block_until_with_gui():    _run_with_wx(   _test_block_until)
+def test_block_until_without_gui(): _run_without_wx(_test_block_until)
+def _test_block_until():
+    ev = threading.Event()
+
+    def task():
+        time.sleep(1)
+        ev.set()
+
+    threading.Thread(target=task).start()
+
+    start = time.time()
+    idle.block(3, until=ev.is_set)
+    end = time.time()
+
+    assert end - start < 3
+
+
 @pytest.mark.wxtest
 def test_idle():
 
diff --git a/tests/test_platform.py b/tests/test_platform.py
index a747ac03a3ae0d72fc0bc9397da0aa2a10afd613..6dcbfaeeab8a7f7a1ad04732f6ac810bd30992d0 100644
--- a/tests/test_platform.py
+++ b/tests/test_platform.py
@@ -59,6 +59,7 @@ def test_haveGui():
     wx.CallLater(500, runtest)
 
     app.MainLoop()
+    del app
 
     assert passed[0]