diff --git a/fsl/utils/async.py b/fsl/utils/async.py index 67e0bf65e9507d97a4bf5100779bbdffaa1565f8..60734afb461c3dd35ec4b65bdba1ab414e0194f6 100644 --- a/fsl/utils/async.py +++ b/fsl/utils/async.py @@ -163,7 +163,7 @@ def run(task, onFinish=None, onError=None, name=None): _idleRegistered = False -"""Boolean flag indicating whether the :func:`wxIdleLoop` function has +"""Boolean flag indicating whether the :func:`_wxIdleLoop` function has been registered as a ``wx.EVT_IDLE`` event handler. Checked and set in the :func:`idle` function. """ @@ -182,6 +182,20 @@ currently queued on the idle loop (see the ``name`` parameter to the """ +_idleTimer = None +"""A ``wx.Timer`` instance which is used to periodically trigger the +:func:`_wxIdleLoop` in circumstances where ``wx.EVT_IDLE`` events may not +be generated. This is created in the first call to :func:`idle`. +""" + + +_idleCallRate = 200 +"""Minimum time (in milliseconds) between consecutive calls to +:func:`_wxIdleLoop`. If ``wx.EVT_IDLE`` events are not being fired, the +:attr:`_idleTimer` is used to maintain the idle loop at this rate. +""" + + class IdleTask(object): """Container object used by the :func:`idle` and :func:`_wxIdleLoop` functions. @@ -204,20 +218,35 @@ class IdleTask(object): self.kwargs = kwargs - def _wxIdleLoop(ev): - """Function which is called on ``wx.EVT_IDLE`` events. If there + """Function which is called on ``wx.EVT_IDLE`` events, and occasionally + on ``wx.EVT_TIMER` events via the :attr:`_idleTimer`. If there is a function on the :attr:`_idleQueue`, it is popped and called. + + .. note:: The ``wx.EVT_IDLE`` event is only triggered on user interaction + (e.g. mouse movement). This means that a situation may arise + whereby a function is queued via the :func:`idle` function, but + no ``EVT_IDLE`` event gets generated. Therefore, the + :attr:`_idleTimer` object is occasionally used to call this + function as well. """ + import wx global _idleQueue global _idleQueueSet - + global _idleTimer + global _idleCallRate + ev.Skip() try: task = _idleQueue.get_nowait() + except queue.Empty: + + # Make sure that we get called periodically, + # if EVT_IDLE decides to stop firing. + _idleTimer.Start(_idleCallRate, wx.TIMER_ONE_SHOT) return now = time.time() @@ -243,6 +272,8 @@ def _wxIdleLoop(ev): if _idleQueue.qsize() > 0: ev.RequestMore() + else: + _idleTimer.Start(_idleCallRate, wx.TIMER_ONE_SHOT) def inIdle(taskName): @@ -288,6 +319,7 @@ def idle(task, *args, **kwargs): """ global _idleRegistered + global _idleTimer global _idleQueue global _idleQueueSet @@ -300,9 +332,14 @@ def idle(task, *args, **kwargs): import wx if not _idleRegistered: - wx.GetApp().Bind(wx.EVT_IDLE, _wxIdleLoop) + app = wx.GetApp() + app.Bind(wx.EVT_IDLE, _wxIdleLoop) + + _idleTimer = wx.Timer(app) _idleRegistered = True + _idleTimer.Bind(wx.EVT_TIMER, _wxIdleLoop) + log.debug('Scheduling idle task ({}) on wx idle ' 'loop'.format(getattr(task, '__name__', '<unknown>'))) @@ -558,7 +595,7 @@ class TaskThread(threading.Thread): getattr(task.func, '__name__', '<unknown>'))) except Exception as e: - log.debug('Task crashed: {} [{}]: {}: {}'.format( + log.warning('Task crashed: {} [{}]: {}: {}'.format( task.name, getattr(task.func, '__name__', '<unknown>'), type(e).__name__,