fsl.utils.idle

This module provides functions and classes for running tasks asynchronously, either in an idle loop, or on a separate thread.

Note

The IdleLoop functionality in this module is intended to be run from within a wx application. However, it will still work without wx, albeit with slightly modified behaviour.

Idle tasks

IdleLoop

This class contains logic for running tasks via wx.EVT_IDLE events.

idle

Equivalent to calling IdleLoop.idle() on the idleLoop singleton.

idleWhen

Equivalent to calling IdleLoop.idleWhen() on the idleLoop singleton.

block

Blocks for the specified number of seconds, yielding to the main wx loop.

The IdleLoop class provides a simple way to run a task on an wx EVT_IDLE event handler. A single IdleLoop instance is created when this module is imported; it can be accessed via the idleLoop attribute, and via the module-level idle() and idleWhen() functions.

The IdleLoop.idle() method effectively performs the same job as the run() function (described below), but is more suitable for short tasks which do not warrant running in a separate thread.

Thread tasks

run

Run the given task in a separate thread.

wait

Creates and starts a new Thread which waits for all of the Thread instances to finish (by join``ing them), and then runs the given ``task via idle().

TaskThread

The TaskThread is a simple thread which runs tasks.

The run() function simply runs a task in a separate thread. This doesn’t seem like a worthy task to have a function of its own, but the run() function additionally provides the ability to schedule another function to run on the wx.MainLoop when the original function has completed (via idle()). This therefore gives us a simple way to run a computationally intensitve task off the main GUI thread (preventing the GUI from locking up), and to perform some clean up/refresh/notification afterwards.

The wait() function is given one or more Thread instances, and a task to run. It waits until all the threads have finished, and then runs the task (via idle()).

The TaskThread class is a simple thread which runs a queue of tasks.

Other facilities

The idle module also defines the mutex() decorator, which is intended to be used to mark the methods of a class as being mutually exclusive. The mutex decorator uses the MutexFactory class to do its work.

fsl.utils.idle._canHaveGui()[source]

Return True if wxPython is installed, and a display is available, False otherwise.

fsl.utils.idle._haveGui()[source]

Return True if wxPython is installed, a display is available, and a wx.App exists, False otherwise.

class fsl.utils.idle.IdleTask(name, task, schedtime, after, timeout, args, kwargs)[source]

Bases: object

Container object used by the IdleLoop class. Used to encapsulate information about a queued task.

__init__(name, task, schedtime, after, timeout, args, kwargs)[source]
__dict__ = mappingproxy({'__module__': 'fsl.utils.idle', '__doc__': 'Container object used by the :class:`IdleLoop` class.\n    Used to encapsulate information about a queued task.\n    ', '__init__': <function IdleTask.__init__>, '__dict__': <attribute '__dict__' of 'IdleTask' objects>, '__weakref__': <attribute '__weakref__' of 'IdleTask' objects>, '__annotations__': {}})
__module__ = 'fsl.utils.idle'
__weakref__

list of weak references to the object (if defined)

class fsl.utils.idle.IdleLoop[source]

Bases: object

This class contains logic for running tasks via wx.EVT_IDLE events.

A single IdleLoop instance is created when this module is first imported - it is accessed via the module-level idleLoop attribute.

In normal circumstances, this idleLoop instance should be treated as a singleton, although this is not enforced in any way.

The EVT_IDLE event is generated automatically by wx during periods of inactivity. However, there are some circumstances in which EVT_IDLE will not be generated, and pending events may be left on the queue. For this reason, the IdleLoop will occasionally use a wx.Timer to ensure that it continues to be called. The time-out used by this Timer can be queried and set via the callRate() property.

__init__()[source]

Create an IdleLoop.

This method does not do much - the real initialisation takes place on the first call to idle().

property registered

Boolean flag indicating whether a handler has been registered on wx.EVT_IDLE events. Checked and set in the idle() method.

property queue

A Queue of functions which are to be run on the wx.EVT_IDLE loop.

property queueDict

A dict containing the names of all named tasks which are currently queued on the idle loop (see the name parameter to the idle() method).

property timer

A wx.Timer instance which is used to periodically trigger the _wxIdleLoop() in circumstances where wx.EVT_IDLE events may not be generated. This is created in the first call to idle().

property callRate

Minimum time (in milliseconds) between consecutive calls to the idle loop (__idleLoop()). If wx.EVT_IDLE events are not being fired, the timer() is used to maintain the idle loop at this rate.

property allowErrors

Used for testing/debugging. If True, and a function called on the idle loop raises an error, that error will not be caught, and the idle loop will stop.

property neverQueue

If True, tasks passed to idle() will never be queued, and instead will always be executed directly/synchonously. See also the synchronous() context manager.

synchronous()[source]

Context manager which can be used to tenporarily set neverQueue() to True, restoring its previous value afterwards.

reset()[source]

Reset the internal idle loop state.

In a normal execution environment, this method will never need to be called. However, in an execution environment where multiple wx.App instances are created, run, and destroyed sequentially, this function will need to be called after each wx.App has been destroyed. Otherwise the idle function will not work during exeution of subsequent wx.App instances.

inIdle(taskName)[source]

Returns True if a task with the given name is queued on the idle loop (or is currently running), False otherwise.

cancelIdle(taskName)[source]

If a task with the given taskName is in the idle queue, it is cancelled. If the task is already running, it cannot be cancelled.

A KeyError is raised if no task called taskName exists.

idle(task, *args, **kwargs)[source]

Run the given task on a wx.EVT_IDLE event.

Parameters:
  • task – The task to run.

  • name – Optional. If provided, must be provided as a keyword argument. Specifies a name that can be used to query the state of this task via inIdle().

  • after – Optional. If provided, must be provided as a keyword argument. A time, in seconds, which specifies the amount of time to wait before running this task after it has been scheduled.

  • timeout – Optional. If provided, must be provided as a keyword argument. Specifies a time out, in seconds. If this amount of time passes before the function gets scheduled to be called on the idle loop, the function is not called, and is dropped from the queue.

  • dropIfQueued – Optional. If provided, must be provided as a keyword argument. If True, and a task with the given name is already enqueud, that function is dropped from the queue, and the new task is enqueued. Defaults to False. This argument takes precedence over the skipIfQueued argument.

  • skipIfQueued – Optional. If provided, must be provided as a keyword argument. If True, and a task with the given name is already enqueud, (or is running), the function is not called. Defaults to False.

  • alwaysQueue – Optional. If provided, must be provided as a keyword 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, 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.

If a wx.App is not running, or neverQueue() has been set to True, the timeout, name, dropIfQueued, skipIfQueued, and alwaysQueue arguments are ignored. Instead, the call will sleep for after seconds, and then the task will be called directly.

Note

If the after argument is used, there is no guarantee that the task will be executed in the order that it is scheduled. This is because, if the required time has not elapsed when the task is popped from the queue, it will be re-queued.

Note

If you schedule multiple tasks with the same name, and you do not use the skipIfQueued or dropIfQueued arguments, all of those tasks will be executed, but you will only be able to query/cancel the most recently enqueued task.

Note

You will run into difficulties if you schedule a function that expects/accepts its own keyword arguments called name, skipIfQueued, dropIfQueued, after, timeout, or alwaysQueue.

idleWhen(func, condition, *args, **kwargs)[source]

Poll the condition function periodically, and schedule func on idle() when it returns True.

Parameters:
  • func – Function to call.

  • condition – Function which returns True or False. The func function is only called when the condition function returns True.

  • pollTime – Must be passed as a keyword argument. Time (in seconds) to wait between successive calls to when. Defaults to 0.2.

__idleLoop(ev)

This method is called on wx.EVT_IDLE events, and occasionally on wx.EVT_TIMER events via the timer(). If there is a function on the queue(), 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 idle() method, but no EVT_IDLE event gets generated. Therefore, the timer() object is occasionally used to call this function as well.

__dict__ = mappingproxy({'__module__': 'fsl.utils.idle', '__doc__': 'This class contains logic for running tasks via ``wx.EVT_IDLE`` events.\n\n    A single ``IdleLoop`` instance is created when this module is first\n    imported - it is accessed via the module-level :attr:`idleLoop` attribute.\n\n    In normal circumstances, this ``idleLoop`` instance should be treated as a\n    singleton, although this is not enforced in any way.\n\n    The ``EVT_IDLE`` event is generated automatically by ``wx`` during periods\n    of inactivity. However, there are some circumstances in which ``EVT_IDLE``\n    will not be generated, and pending events may be left on the queue. For\n    this reason, the ``IdleLoop`` will occasionally use a ``wx.Timer`` to\n    ensure that it continues to be called. The time-out used by this ``Timer``\n    can be queried and set via the :meth:`callRate` property.\n    ', '__init__': <function IdleLoop.__init__>, 'registered': <property object>, 'queue': <property object>, 'queueDict': <property object>, 'timer': <property object>, 'callRate': <property object>, 'allowErrors': <property object>, 'neverQueue': <property object>, 'synchronous': <function IdleLoop.synchronous>, 'reset': <function IdleLoop.reset>, 'inIdle': <function IdleLoop.inIdle>, 'cancelIdle': <function IdleLoop.cancelIdle>, 'idle': <function IdleLoop.idle>, 'idleWhen': <function IdleLoop.idleWhen>, '_IdleLoop__idleLoop': <function IdleLoop.__idleLoop>, '__dict__': <attribute '__dict__' of 'IdleLoop' objects>, '__weakref__': <attribute '__weakref__' of 'IdleLoop' objects>, '__annotations__': {}})
__module__ = 'fsl.utils.idle'
__weakref__

list of weak references to the object (if defined)

fsl.utils.idle.idleLoop = <fsl.utils.idle.IdleLoop object>

A singleton IdleLoop instance, created when this module is imported.

fsl.utils.idle.idle(*args, **kwargs)[source]

Equivalent to calling IdleLoop.idle() on the idleLoop singleton.

fsl.utils.idle.idleWhen(*args, **kwargs)[source]

Equivalent to calling IdleLoop.idleWhen() on the idleLoop singleton.

fsl.utils.idle.block(secs, delta=0.01, until=None)[source]

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.

Parameters:
  • secs – Time in seconds to block

  • delta – Time in seconds to sleep between successive yields to wx.

  • until – Function which returns True or False, and which determins when calls to block will return.

fsl.utils.idle.run(task, onFinish=None, onError=None, name=None)[source]

Run the given task in a separate thread.

Parameters:
  • task – The function to run. Must accept no arguments.

  • onFinish – An optional function to schedule (on the wx.MainLoop, via idle()) once the task has finished.

  • onError – An optional function to be called (on the wx.MainLoop, via idle()) if the task raises an error. Passed the Exception that was raised.

  • name – An optional name to use for this task in log statements.

Returns:

A reference to the Thread that was created.

Note

If a wx application is not running, the task and onFinish functions will simply be called directly, and the return value will be None.

fsl.utils.idle.wait(threads, task, *args, **kwargs)[source]

Creates and starts a new Thread which waits for all of the Thread instances to finish (by join``ing them), and then runs the given ``task via idle().

If the direct parameter is True, or a wx.App is not running, this function join``s the threads directly instead of creating a new ``Thread to do so.

Parameters:
  • threads – A Thread, or a sequence of Thread instances to join. Elements in the sequence may be None.

  • task – The task to run once all threads have completed.

  • wait_direct – Must be passed as a keyword argument. If True, this function call will join all of the threads, and then call the task. Otherwise (the default), this function will create a new thread to join the threads, and will return immediately.

All other arguments are passed to the task function.

Note

This function will not support task functions which expect a keyword argument called wait_direct.

class fsl.utils.idle.Task(name, func, onFinish, onError, args, kwargs)[source]

Bases: object

Container object which encapsulates a task that is run by a TaskThread.

__init__(name, func, onFinish, onError, args, kwargs)[source]
__dict__ = mappingproxy({'__module__': 'fsl.utils.idle', '__doc__': 'Container object which encapsulates a task that is run by a\n    :class:`TaskThread`.\n    ', '__init__': <function Task.__init__>, '__dict__': <attribute '__dict__' of 'Task' objects>, '__weakref__': <attribute '__weakref__' of 'Task' objects>, '__annotations__': {}})
__module__ = 'fsl.utils.idle'
__weakref__

list of weak references to the object (if defined)

exception fsl.utils.idle.TaskThreadVeto[source]

Bases: Exception

Task functions which are added to a TaskThread may raise a TaskThreadVeto error to skip processing of the task’s onFinish handler (if one has been specified). See the TaskThread.enqueue() method for more details.

__module__ = 'fsl.utils.idle'
__weakref__

list of weak references to the object (if defined)

class fsl.utils.idle.TaskThread(*args, **kwargs)[source]

Bases: Thread

The TaskThread is a simple thread which runs tasks. Tasks may be enqueued and dequeued.

__init__(*args, **kwargs)[source]

Create a TaskThread.

enqueue(func, *args, **kwargs)[source]

Enqueue a task to be executed.

Parameters:
  • func – The task function.

  • taskName – Task name. Must be specified as a keyword argument. Does not necessarily have to be a string, but must be hashable. If you wish to use the dequeue() or isQueued() methods, you must provide a task name.

  • onFinish – An optional function to be called (via idle()) when the task funtion has finished. Must be provided as a keyword argument, and must itself accept no arguments. If the func raises a :class`TaskThreadVeto` error, this function will not be called.

  • onError – An optional function to be called (via idle()) if the task funtion raises an Exception. Must be provided as a keyword argument, and must itself accept the raised Exception object as a single argument. If the func raises a :class`TaskThreadVeto` error, this function will not be called.

All other arguments are passed through to the task function when it is executed.

Note

If the specified taskName is not unique (i.e. another task with the same name may already be enqueued), the isQueued() method will probably return invalid results.

Warning

Make sure that your task function is not expecting keyword arguments called taskName, onFinish, or onError!

isQueued(name)[source]

Returns True if a task with the given name is enqueued, False otherwise.

dequeue(name)[source]

Dequeues a previously enqueued task.

Parameters:

name – The task to dequeue.

stop()[source]

Stop the TaskThread after any currently running task has completed.

waitUntilIdle()[source]

Causes the calling thread to block until the task queue is empty.

run()[source]

Run the TaskThread.

__module__ = 'fsl.utils.idle'
fsl.utils.idle.mutex(*args, **kwargs)[source]

Decorator for use on methods of a class, which makes the method call mutually exclusive.

If you define a class which has one or more methods that must only be accessed by one thread at a time, you can use the mutex decorator to enforce this restriction. As a contrived example:

class Example(object):

    def __init__(self):
        self.__sharedData = []

    @mutex
    def dangerousMethod1(self, message):
        sefl.__sharedData.append(message)

    @mutex
    def dangerousMethod2(self):
        return sefl.__sharedData.pop()

The @mutex decorator will ensure that, at any point in time, only one thread is running either of the dangerousMethod1 or dangerousMethod2 methods.

See the MutexFactory`

class fsl.utils.idle.MutexFactory(function)[source]

Bases: object

The MutexFactory is a placeholder for methods which have been decorated with the mutex() decorator. When the method of a class is decorated with @mutex, a MutexFactory is created.

Later on, when the method is accessed on an instance, the __get__() method creates the true decorator function, and replaces the instance method with that decorator.

Note

The MutexFactory adds an attribute called _async_mutex_lock to all instances that have @mutex-decorated methods.

__dict__ = mappingproxy({'__module__': 'fsl.utils.idle', '__doc__': 'The ``MutexFactory`` is a placeholder for methods which have been\n    decorated with the :func:`mutex` decorator. When the method of a class\n    is decorated with ``@mutex``, a ``MutexFactory`` is created.\n\n    Later on, when the method is accessed on an instance, the :meth:`__get__`\n    method creates the true decorator function, and replaces the instance\n    method with that decorator.\n\n    .. note:: The ``MutexFactory`` adds an attribute called\n              ``_async_mutex_lock`` to all instances that have\n              ``@mutex``-decorated methods.\n    ', 'createLock': <unlocked _thread.lock object>, '__init__': <function MutexFactory.__init__>, '__get__': <function MutexFactory.__get__>, '__dict__': <attribute '__dict__' of 'MutexFactory' objects>, '__weakref__': <attribute '__weakref__' of 'MutexFactory' objects>, '__annotations__': {}})
__module__ = 'fsl.utils.idle'
__weakref__

list of weak references to the object (if defined)

createLock = <unlocked _thread.lock object>

This lock is used by all MutexFactory instances when a decorated instance method is accessed for the first time.

The first time that a mutexed method is accessed on an instance, a new threading.Lock is created, to be shared by all mutexed methods of that instance. The createLock is used to ensure that this can only occur once for each instance.

__init__(function)[source]

Create a MutexFactory.

__get__(instance, cls)[source]

When this MutexFactory is accessed through an instance, a decorator function is created which enforces mutually exclusive access to the decorated method. A single threading.Lock object is shared between all @mutex-decorated methods on a single instance.

If this MutexFactory is accessed through a class, the decorated function is returned.