Skip to content
Snippets Groups Projects
Commit c21c83f1 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'master' into 'master'

Adjustment to async.idle

See merge request !10
parents 99764df5 a1ed48e9
No related branches found
No related tags found
No related merge requests found
Pipeline #
...@@ -96,14 +96,6 @@ except: import Queue as queue ...@@ -96,14 +96,6 @@ except: import Queue as queue
log = logging.getLogger(__name__) 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): def run(task, onFinish=None, onError=None, name=None):
"""Run the given ``task`` in a separate thread. """Run the given ``task`` in a separate thread.
...@@ -125,10 +117,12 @@ def run(task, onFinish=None, onError=None, name=None): ...@@ -125,10 +117,12 @@ def run(task, onFinish=None, onError=None, name=None):
the return value will be ``None``. the return value will be ``None``.
""" """
from fsl.utils.platform import platform as fslplatform
if name is None: if name is None:
name = getattr(task, '__name__', '<unknown>') name = getattr(task, '__name__', '<unknown>')
haveWX = _haveWX() haveWX = fslplatform.haveGui
# Calls the onFinish or onError handler # Calls the onFinish or onError handler
def callback(cb, *args, **kwargs): def callback(cb, *args, **kwargs):
...@@ -397,9 +391,11 @@ def idle(task, *args, **kwargs): ...@@ -397,9 +391,11 @@ def idle(task, *args, **kwargs):
argument. If ``True``, and a ``wx.MainLoop`` is not argument. If ``True``, and a ``wx.MainLoop`` is not
running, the task is enqueued anyway, under the running, the task is enqueued anyway, under the
assumption that a ``wx.MainLoop`` will be started in assumption that a ``wx.MainLoop`` will be started in
the future. Note that another call to ``idle`` must the future. Note that, if ``wx.App`` has not yet been
be made after the ``MainLoop`` has started for the created, another call to ``idle`` must be made after
original task to be executed. 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. All other arguments are passed through to the task function.
...@@ -426,6 +422,8 @@ def idle(task, *args, **kwargs): ...@@ -426,6 +422,8 @@ def idle(task, *args, **kwargs):
``alwaysQueue``. ``alwaysQueue``.
""" """
from fsl.utils.platform import platform as fslplatform
global _idleRegistered global _idleRegistered
global _idleTimer global _idleTimer
global _idleQueue global _idleQueue
...@@ -439,13 +437,29 @@ def idle(task, *args, **kwargs): ...@@ -439,13 +437,29 @@ def idle(task, *args, **kwargs):
skipIfQueued = kwargs.pop('skipIfQueued', False) skipIfQueued = kwargs.pop('skipIfQueued', False)
alwaysQueue = kwargs.pop('alwaysQueue', 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 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) app.Bind(wx.EVT_IDLE, _wxIdleLoop)
_idleTimer = wx.Timer(app) _idleTimer = wx.Timer(app)
...@@ -546,12 +560,14 @@ def wait(threads, task, *args, **kwargs): ...@@ -546,12 +560,14 @@ def wait(threads, task, *args, **kwargs):
a keyword argument called ``wait_direct``. a keyword argument called ``wait_direct``.
""" """
from fsl.utils.platform import platform as fslplatform
direct = kwargs.pop('wait_direct', False) direct = kwargs.pop('wait_direct', False)
if not isinstance(threads, collections.Sequence): if not isinstance(threads, collections.Sequence):
threads = [threads] threads = [threads]
haveWX = _haveWX() haveWX = fslplatform.haveGui
def joinAll(): def joinAll():
log.debug('Wait thread joining on all targets') log.debug('Wait thread joining on all targets')
......
...@@ -9,29 +9,46 @@ import time ...@@ -9,29 +9,46 @@ import time
import threading import threading
import random import random
from six.moves import reload_module
import pytest import pytest
import mock
import fsl.utils.async as async import fsl.utils.async as async
from fsl.utils.platform import platform as fslplatform 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): def _run_with_wx(func, *args, **kwargs):
global _wxapp
propagateRaise = kwargs.pop('propagateRaise', True) propagateRaise = kwargs.pop('propagateRaise', True)
startingDelay = kwargs.pop('startingDelay', 500) startingDelay = kwargs.pop('startingDelay', 500)
finishingDelay = kwargs.pop('finishingDelay', 500) finishingDelay = kwargs.pop('finishingDelay', 500)
callAfterApp = kwargs.pop('callAfterApp', None)
import wx
import wx
result = [None] result = [None]
raised = [None] raised = [None]
app = wx.App()
if _wxapp is None:
_wxapp = wx.App()
frame = wx.Frame(None) frame = wx.Frame(None)
if callAfterApp is not None:
callAfterApp()
def wrap(): def wrap():
try: try:
result[0] = func(*args, **kwargs) if func is not None:
result[0] = func(*args, **kwargs)
except Exception as e: except Exception as e:
print(e) print(e)
...@@ -40,14 +57,14 @@ def _run_with_wx(func, *args, **kwargs): ...@@ -40,14 +57,14 @@ def _run_with_wx(func, *args, **kwargs):
finally: finally:
def finish(): def finish():
frame.Destroy() frame.Destroy()
app.ExitMainLoop() _wxapp.ExitMainLoop()
wx.CallLater(finishingDelay, finish) wx.CallLater(finishingDelay, finish)
frame.Show() frame.Show()
wx.CallLater(startingDelay, wrap) wx.CallLater(startingDelay, wrap)
app.MainLoop() _wxapp.MainLoop()
async.idleReset() async.idleReset()
if raised[0] and propagateRaise: if raised[0] and propagateRaise:
...@@ -244,25 +261,98 @@ def test_idle_dropIfQueued(): ...@@ -244,25 +261,98 @@ def test_idle_dropIfQueued():
assert task2called[0] assert task2called[0]
def test_idle_alwaysQueue(): def test_idle_alwaysQueue1():
# Test scheduling the task before
# a wx.App has been created.
called = [False] called = [False]
def task(): def task():
called[0] = True 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(): def nop():
pass pass
# The task should be run
# when the mainloop starts
async.idle(task, alwaysQueue=True) async.idle(task, alwaysQueue=True)
# We need to queue another task # Second call to async.idle
# for the first task to be executed
_run_with_wx(async.idle, nop) _run_with_wx(async.idle, nop)
assert called[0] 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(): def test_idle_timeout():
called = [False] called = [False]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment