Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
F
fslpy
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Container Registry
Model registry
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Michiel Cottaar
fslpy
Commits
0d07fa79
Commit
0d07fa79
authored
5 years ago
by
Paul McCarthy
Browse files
Options
Downloads
Patches
Plain Diff
RF: Reorganised idle loop logic into a single class. Deprecated various
module-level functions. No changes to functionality
parent
47067c1d
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
fsl/utils/idle.py
+491
-400
491 additions, 400 deletions
fsl/utils/idle.py
fsl/utils/platform.py
+8
-1
8 additions, 1 deletion
fsl/utils/platform.py
with
499 additions
and
401 deletions
fsl/utils/idle.py
+
491
−
400
View file @
0d07fa79
...
@@ -8,9 +8,9 @@
...
@@ -8,9 +8,9 @@
asynchronously, either in an idle loop, or on a separate thread.
asynchronously, either in an idle loop, or on a separate thread.
.. note:: The
*idle*
function
s
in this module
are
intended to be
run from
.. note:: The
:class:`IdleLoop`
function
ality
in this module
is
intended to be
within a ``wx`` application. However, t
hey
will still work
without
run from
within a ``wx`` application. However,
i
t will still work
``wx``, albeit with slightly modified behaviour.
without
``wx``, albeit with slightly modified behaviour.
Idle tasks
Idle tasks
...
@@ -19,28 +19,20 @@ Idle tasks
...
@@ -19,28 +19,20 @@ Idle tasks
.. autosummary::
.. autosummary::
:nosignatures:
:nosignatures:
block
IdleLoop
idle
idle
idleWhen
idleWhen
inIdle
block
cancelIdle
idleReset
getIdleTimeout
setIdleTimeout
The :func:`idle` function is a simple way to run a task on an ``wx``
``EVT_IDLE`` event handler. This effectively performs the same job as the
:func:`run` function, but is more suitable for short tasks which do not
warrant running in a separate thread.
The :class:`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 :attr:`idleLoop` attribute,
and via the module-level :func:`idle` and :func:`idleWhen` functions.
The ``EVT_IDLE`` event is generated automatically by ``wx``. However, there
The :meth:`IdleLoop.idle` method effectively performs the same job as the
are some circumstances in which ``EVT_IDLE`` will not be generated, and
:func:`run` function (described below), but is more suitable for short tasks
pending events may be left on the queue. For this reason, the
which do not warrant running in a separate thread.
:func:`_wxIdleLoop` 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 :func:`getIdleTimeout` and :func:`setIdleTimeout` functions.
Thread tasks
Thread tasks
...
@@ -58,9 +50,10 @@ The :func:`run` function simply runs a task in a separate thread. This
...
@@ -58,9 +50,10 @@ The :func:`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
doesn
'
t seem like a worthy task to have a function of its own, but the
:func:`run` function additionally provides the ability to schedule another
:func:`run` function additionally provides the ability to schedule another
function to run on the ``wx.MainLoop`` when the original function has
function to run on the ``wx.MainLoop`` when the original function has
completed. This therefore gives us a simple way to run a computationally
completed (via :func:`idle`). This therefore gives us a simple way to run a
intensitve task off the main GUI thread (preventing the GUI from locking up),
computationally intensitve task off the main GUI thread (preventing the GUI
and to perform some clean up/refresh afterwards.
from locking up), and to perform some clean up/refresh/notification
afterwards.
The :func:`wait` function is given one or more ``Thread`` instances, and a
The :func:`wait` function is given one or more ``Thread`` instances, and a
...
@@ -91,304 +84,512 @@ from collections import abc
...
@@ -91,304 +84,512 @@ from collections import abc
try
:
import
queue
try
:
import
queue
except
ImportError
:
import
Queue
as
queue
except
ImportError
:
import
Queue
as
queue
from
fsl.utils.deprecated
import
deprecated
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
def
run
(
task
,
onFinish
=
None
,
onError
=
None
,
name
=
None
):
class
IdleTask
(
object
):
"""
Run the given ``task`` in a separate thread.
"""
Container object used by the :class:`IdleLoop` class.
Used to encapsulate information about a queued task.
"""
:arg task: The function to run. Must accept no arguments.
def
__init__
(
self
,
name
,
task
,
schedtime
,
after
,
timeout
,
args
,
kwargs
):
self
.
name
=
name
self
.
task
=
task
self
.
schedtime
=
schedtime
self
.
after
=
after
self
.
timeout
=
timeout
self
.
args
=
args
self
.
kwargs
=
kwargs
:arg onFinish: An optional function to schedule (on the ``wx.MainLoop``,
via :func:`idle`) once the ``task`` has finished.
:arg onError: An optional function to be called (on the ``wx.MainLoop``,
class
IdleLoop
(
object
):
via :func:`idle`) if the ``task`` raises an error. Passed
"""
This class contains logic for running tasks via ``wx.EVT_IDLE`` events.
the ``Exception`` that was raised.
:arg name: An optional name to use for this task in log statements.
A single ``IdleLoop`` instance is created when this module is first
imported - it is accessed via the module-level :attr:`idleLoop` attribute.
:returns: A reference to the ``Thread`` that was created.
In normal circumstances, this ``idleLoop`` instance should be treated as a
singleton, although this is not enforced in any way.
.. note:: If a ``wx`` application is not running, the ``task`` and
The ``EVT_IDLE`` event is generated automatically by ``wx`` during periods
``onFinish`` functions will simply be called directly, and
of inactivity. However, there are some circumstances in which ``EVT_IDLE``
the return value will be ``None``.
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 :meth:`callRate` property.
"""
"""
from
fsl.utils.platform
import
platform
as
fslplatform
def
__init__
(
self
):
"""
Create an ``IdleLoop``.
if
name
is
None
:
This method does not do much - the real initialisation takes place
name
=
getattr
(
task
,
'
__name__
'
,
'
<unknown>
'
)
on the first call to :meth:`idle`.
"""
self
.
__registered
=
False
self
.
__queue
=
queue
.
Queue
()
self
.
__queueDict
=
{}
self
.
__timer
=
None
self
.
__callRate
=
200
self
.
__allowErrors
=
False
# Call reset on exit, in case
# the idle.timer is active.
atexit
.
register
(
self
.
reset
)
@property
def
registered
(
self
):
"""
Boolean flag indicating whether a handler has been registered on
``wx.EVT_IDLE`` events. Checked and set in the :meth:`idle` method.
"""
return
self
.
__registered
haveWX
=
fslplatform
.
haveGui
# Calls the onFinish or onError handler
@property
def
callback
(
cb
,
*
args
,
**
kwargs
):
def
queue
(
self
):
"""
A ``Queue`` of functions which are to be run on the ``wx.EVT_IDLE``
loop.
"""
return
self
.
__queue
if
cb
is
None
:
return
if
haveWX
:
idle
(
cb
,
*
args
,
**
kwargs
)
@property
else
:
cb
(
*
args
,
**
kwargs
)
def
queueDict
(
self
):
"""
A ``dict`` containing the names of all named tasks which are
currently queued on the idle loop (see the ``name`` parameter to the
:meth:`idle` method).
"""
return
self
.
__queueDict
# Runs the task, and calls
# callback functions as needed.
def
wrapper
():
try
:
@property
task
()
def
timer
(
self
):
log
.
debug
(
'
Task
"
{}
"
finished
'
.
format
(
name
))
"""
A ``wx.Timer`` instance which is used to periodically trigger the
callback
(
onFinish
)
:func:`_wxIdleLoop` in circumstances where ``wx.EVT_IDLE`` events may
not be generated. This is created in the first call to :meth:`idle`.
"""
return
self
.
__timer
except
Exception
as
e
:
log
.
warning
(
'
Task
"
{}
"
crashed
'
.
format
(
name
),
exc_info
=
True
)
@property
callback
(
onError
,
e
)
def
callRate
(
self
):
"""
Minimum time (in milliseconds) between consecutive calls to the idle
loop (:meth:`__idleLoop`). If ``wx.EVT_IDLE`` events are not being
fired, the :meth:`timer` is used to maintain the idle loop at this
rate.
"""
return
self
.
__callRate
# If WX, run on a thread
if
haveWX
:
log
.
debug
(
'
Running task
"
{}
"
on thread
'
.
format
(
name
))
@callRate.setter
def
callRate
(
self
,
rate
):
"""
Update the :meth:`callRate` to ``rate`` (specified in milliseconds).
thread
=
threading
.
Thread
(
target
=
wrapper
)
If ``rate is None``, it is set to the default of 200 milliseconds.
thread
.
start
()
"""
return
thread
# Otherwise run directly
if
rate
is
None
:
else
:
rate
=
200
log
.
debug
(
'
Running task
"
{}
"
directly
'
.
format
(
name
))
wrapper
()
return
None
log
.
debug
(
'
Idle loop timeout changed to {}
'
.
format
(
rate
))
_idleRegistered
=
False
self
.
__callRate
=
rate
"""
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.
"""
_idleQueue
=
queue
.
Queue
()
@property
"""
A ``Queue`` of functions which are to be run on the ``wx.EVT_IDLE``
def
allowErrors
(
self
):
loop.
"""
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.
"""
return
self
.
__allowErrors
_idleQueueDict
=
{}
@allowErrors.setter
"""
A ``dict`` containing the names of all named tasks which are
def
allowErrors
(
self
,
allow
):
currently queued on the idle loop (see the ``name`` parameter to the
"""
Update the ``allowErrors`` flag.
"""
:func:`idle` function).
self
.
__allowErrors
=
allow
"""
_idleTimer
=
None
def
reset
(
self
):
"""
A ``wx.Timer`` instance which is used to periodically trigger the
"""
Reset the internal idle loop state.
:func:`_wxIdleLoop` in circumstances where ``wx.EVT_IDLE`` events may not
be generated. This is created in the first call to :func:`idle`.
"""
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.
"""
_idleCallRate
=
200
if
self
.
__timer
is
not
None
:
"""
Minimum time (in milliseconds) between consecutive calls to
self
.
__timer
.
Stop
()
:func:`_wxIdleLoop`. If ``wx.EVT_IDLE`` events are not being fired, the
:attr:`_idleTimer` is used to maintain the idle loop at this rate.
"""
# If we're atexit, the ref to
# the queue module might have
# been cleared, in which case
# we don't want to create a
# new one.
if
self
.
__queue
is
not
None
:
newQueue
=
queue
.
Queue
()
else
:
newQueue
=
None
_idleAllowErrors
=
False
self
.
__registered
=
False
"""
Used for testing/debugging. If ``True``, and a function called on the idle
self
.
__queue
=
newQueue
loop raises an error, that error will not be caught, and the idle loop will
self
.
__queueDict
=
{}
stop.
self
.
__timer
=
None
"""
self
.
__callRate
=
200
self
.
__allowErrors
=
False
def
idleReset
():
def
inIdle
(
self
,
taskName
):
"""
Reset the internal :func:`idle` queue state.
"""
Returns ``True`` if a task with the given name is queued on the
idle loop (or is currently running), ``False`` otherwise.
In a normal execution environment, this function will never need to be
"""
called. However, in an execution environment where multiple ``wx.App``
return
taskName
in
self
.
__queueDict
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.
"""
global
_idleRegistered
global
_idleQueue
global
_idleQueueDict
global
_idleTimer
global
_idleCallRate
global
_idleAllowErrors
if
_idleTimer
is
not
None
:
_idleTimer
.
Stop
()
# If we're atexit, the ref
def
cancelIdle
(
self
,
taskName
):
# to the queue module might
"""
If a task with the given ``taskName`` is in the idle queue, it
# have been cleared.
is cancelled. If the task is already running, it cannot be cancelled.
if
queue
is
not
None
:
newQueue
=
queue
.
Queue
()
else
:
newQueue
=
None
_idleRegistered
=
False
A ``KeyError`` is raised if no task called ``taskName`` exists.
_idleQueue
=
newQueue
"""
_idleQueueDict
=
{}
self
.
__queueDict
[
taskName
].
timeout
=
-
1
_idleTimer
=
None
_idleCallRate
=
200
_idleAllowErrors
=
False
# Call idleReset on exit, in
def
idle
(
self
,
task
,
*
args
,
**
kwargs
):
# case the idleTimer is active.
"""
Run the given task on a ``wx.EVT_IDLE`` event.
atexit
.
register
(
idleReset
)
:arg task: The task to run.
def
getIdleTimeout
():
:arg name: Optional. If provided, must be provided as a keyword
"""
Returns the current ``wx`` idle loop time out/call rate.
argument. Specifies a name that can be used to
"""
query the state of this task via :meth:`inIdle`.
return
_idleCallRate
:arg 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.
def
setIdleTimeout
(
timeout
=
None
):
:arg timeout: Optional. If provided, must be provided as a keyword
"""
Set the ``wx`` idle loop time out/call rate. If ``timeout`` is not
argument. Specifies a time out, in seconds. If this
provided, or is set to ``None``, the timeout is set to 200 milliseconds.
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.
global
_idleCallRate
:arg 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.
if
timeout
is
None
:
:arg skipIfQueued: Optional. If provided, must be provided as a keyword
timeout
=
200
argument. If ``True``, and a task with the given
``name`` is already enqueud, (or is running), the
function is not called. Defaults to ``False``.
log
.
debug
(
'
Idle loop timeout changed to {}
'
.
format
(
timeout
))
:arg 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.
_idleCallRate
=
timeout
All other arguments are passed through to the task function.
class
IdleTask
(
object
):
"""
Container object used by the :func:`idle` and :func:`_wxIdleLoop`
functions.
"""
def
__init__
(
self
,
If a ``wx.App`` is not running, the ``timeout``, ``name`` and
name
,
``skipIfQueued`` arguments are ignored. Instead, the call will sleep
task
,
for ``after`` seconds, and then the ``task`` is called directly.
schedtime
,
after
,
timeout
,
args
,
kwargs
):
self
.
name
=
name
self
.
task
=
task
self
.
schedtime
=
schedtime
self
.
after
=
after
self
.
timeout
=
timeout
self
.
args
=
args
self
.
kwargs
=
kwargs
def
_wxIdleLoop
(
ev
):
.. note:: If the ``after`` argument is used, there is no guarantee that
"""
Function which is called on ``wx.EVT_IDLE`` events, and occasionally
the task will be executed in the order that it is scheduled.
on ``wx.EVT_TIMER`` events via the :attr:`_idleTimer`. If there
This is because, if the required time has not elapsed when
is a function on the :attr:`_idleQ
ueue
`
, it
is popped and call
ed.
the task is popped from the q
ueue, it
will be re-queu
ed.
.. note:: The ``wx.EVT_IDLE`` event is only triggered on user interaction
.. note:: If you schedule multiple tasks with the same ``name``, and
(e.g. mouse movement). This means that a situation may arise
you do not use the ``skipIfQueued`` or ``dropIfQueued``
whereby a function is queued via the :func:`idle` function, but
arguments, all of those tasks will be executed, but you will
no ``EVT_IDLE`` event gets generated. Therefore, the
only be able to query/cancel the most recently enqueued
:attr:`_idleTimer` object is occasionally used to call this
task.
function as well.
"""
.. 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``.
"""
from
fsl.utils.platform
import
platform
as
fslplatform
schedtime
=
time
.
time
()
timeout
=
kwargs
.
pop
(
'
timeout
'
,
0
)
after
=
kwargs
.
pop
(
'
after
'
,
0
)
name
=
kwargs
.
pop
(
'
name
'
,
None
)
dropIfQueued
=
kwargs
.
pop
(
'
dropIfQueued
'
,
False
)
skipIfQueued
=
kwargs
.
pop
(
'
skipIfQueued
'
,
False
)
alwaysQueue
=
kwargs
.
pop
(
'
alwaysQueue
'
,
False
)
canHaveGui
=
fslplatform
.
canHaveGui
haveGui
=
fslplatform
.
haveGui
# If there is no possibility of a
# gui being available in the future
# (determined by canHaveGui), then
# alwaysQueue is ignored.
alwaysQueue
=
alwaysQueue
and
canHaveGui
# We don't have wx - run the task
# directly/synchronously.
if
not
(
haveGui
or
alwaysQueue
):
time
.
sleep
(
after
)
log
.
debug
(
'
Running idle task directly
'
)
task
(
*
args
,
**
kwargs
)
return
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 that is no longer active.
if
(
not
self
.
registered
)
and
(
app
is
not
None
):
log
.
debug
(
'
Registering async idle loop
'
)
app
.
Bind
(
wx
.
EVT_IDLE
,
self
.
__idleLoop
)
# We also occasionally use a
# timer to drive the loop, so
# let's register that as well
self
.
__timer
=
wx
.
Timer
(
app
)
self
.
__timer
.
Bind
(
wx
.
EVT_TIMER
,
self
.
__idleLoop
)
self
.
__registered
=
True
# A task with the specified
# name is already in the queue
if
name
is
not
None
and
self
.
inIdle
(
name
):
# Drop the old task
# with the same name
if
dropIfQueued
:
import
wx
# The cancelIdle function sets the old
global
_idleQueue
# task timeout to -1, so it won't get
global
_idleQueueDict
# executed. But the task is left in the
global
_idleTimer
# queue, and in the queueDict.
global
_idleCallRate
# In the latter, the old task gets
global
_idleAllowErrors
# overwritten with the new task below.
self
.
cancelIdle
(
name
)
ev
.
Skip
()
log
.
debug
(
'
Idle task ({}) is already queued -
'
'
dropping the old task
'
.
format
(
name
))
try
:
task
=
_idleQueue
.
get_nowait
()
# Ignore the new task
# with the same name
except
queue
.
Empty
:
elif
skipIfQueued
:
log
.
debug
(
'
Idle task ({}) is already queued
'
# Make sure that we get called periodically,
'
- skipping it
'
.
format
(
name
))
# if EVT_IDLE decides to stop firing. If
return
# _idleTimer is None, then idleReset has
# probably been called.
log
.
debug
(
'
Scheduling idle task ({}) on wx idle
'
if
_idleTimer
is
not
None
:
'
loop
'
.
format
(
getattr
(
task
,
'
__name__
'
,
'
<unknown>
'
)))
_idleTimer
.
Start
(
_idleCallRate
,
wx
.
TIMER_ONE_SHOT
)
return
idleTask
=
IdleTask
(
name
,
task
,
now
=
time
.
time
()
schedtime
,
elapsed
=
now
-
task
.
schedtime
after
,
queueSizeOffset
=
0
timeout
,
taskName
=
task
.
name
args
,
funcName
=
getattr
(
task
.
task
,
'
__name__
'
,
'
<unknown>
'
)
kwargs
)
if
taskName
is
None
:
taskName
=
funcName
self
.
__queue
.
put_nowait
(
idleTask
)
else
:
taskName
=
'
{} [{}]
'
.
format
(
taskName
,
funcName
)
if
name
is
not
None
:
# Has enough time elapsed
self
.
__queueDict
[
name
]
=
idleTask
# since the task was scheduled?
# If not, re-queue the task.
# If this is the only task on the
def
idleWhen
(
self
,
func
,
condition
,
*
args
,
**
kwargs
):
# queue, the idle loop will be
"""
Poll the ``condition`` function periodically, and schedule ``func``
# called again after
on :meth:`idle` when it returns ``True``.
# _idleCallRate millisecs.
if
elapsed
<
task
.
after
:
:arg func: Function to call.
log
.
debug
(
'
Re-queueing function ({}) on wx idle loop
'
.
format
(
taskName
))
_idleQueue
.
put_nowait
(
task
)
:arg condition: Function which returns ``True`` or ``False``. The
queueSizeOffset
=
1
``func`` function is only called when the
``condition`` function returns ``True``.
# Has the task timed out?
elif
task
.
timeout
==
0
or
(
elapsed
<
task
.
timeout
):
:arg pollTime: Must be passed as a keyword argument. Time (in seconds)
to wait between successive calls to ``when``. Defaults
log
.
debug
(
'
Running function ({}) on wx idle loop
'
.
format
(
taskName
))
to ``0.2``.
"""
pollTime
=
kwargs
.
get
(
'
pollTime
'
,
0.2
)
if
not
condition
():
self
.
idle
(
self
.
idleWhen
,
func
,
condition
,
after
=
pollTime
,
*
args
,
**
dict
(
kwargs
))
else
:
kwargs
.
pop
(
'
pollTime
'
,
None
)
self
.
idle
(
func
,
*
args
,
**
kwargs
)
def
__idleLoop
(
self
,
ev
):
"""
This method is called on ``wx.EVT_IDLE`` events, and occasionally
on ``wx.EVT_TIMER`` events via the :meth:`timer`. If there
is a function on the :meth:`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
:meth:`idle` method, but no ``EVT_IDLE`` event gets
generated. Therefore, the :meth:`timer` object is
occasionally used to call this function as well.
"""
import
wx
ev
.
Skip
()
try
:
try
:
task
.
task
(
*
task
.
args
,
**
task
.
kwargs
)
task
=
self
.
__queue
.
get_nowait
()
except
Exception
as
e
:
log
.
warning
(
'
Idle task {} crashed - {}: {}
'
.
format
(
taskName
,
type
(
e
).
__name__
,
str
(
e
)),
exc_info
=
True
)
if
_idleAllowErrors
:
except
queue
.
Empty
:
raise
e
if
task
.
name
is
not
None
:
# Make sure that we get called periodically,
try
:
_idleQueueDict
.
pop
(
task
.
name
)
# if EVT_IDLE decides to stop firing. If
except
KeyError
:
pass
# self.timer is None, then self.reset has
# probably been called.
if
self
.
__timer
is
not
None
:
self
.
__timer
.
Start
(
self
.
__callRate
,
wx
.
TIMER_ONE_SHOT
)
return
# More tasks on the queue?
now
=
time
.
time
()
# Request anotherd event
elapsed
=
now
-
task
.
schedtime
if
_idleQueue
.
qsize
()
>
queueSizeOffset
:
queueSizeOffset
=
0
ev
.
RequestMore
()
taskName
=
task
.
name
funcName
=
getattr
(
task
.
task
,
'
__name__
'
,
'
<unknown>
'
)
if
taskName
is
None
:
taskName
=
funcName
else
:
taskName
=
'
{} [{}]
'
.
format
(
taskName
,
funcName
)
# Has enough time elapsed
# since the task was scheduled?
# If not, re-queue the task.
# If this is the only task on the
# queue, the idle loop will be
# called again after
# callRate millisecs.
if
elapsed
<
task
.
after
:
log
.
debug
(
'
Re-queueing function ({}) on
'
'
wx idle loop
'
.
format
(
taskName
))
self
.
__queue
.
put_nowait
(
task
)
queueSizeOffset
=
1
# Has the task timed out?
elif
task
.
timeout
==
0
or
(
elapsed
<
task
.
timeout
):
log
.
debug
(
'
Running function ({}) on wx
'
'
idle loop
'
.
format
(
taskName
))
# Otherwise use the idle
try
:
# timer to make sure that
task
.
task
(
*
task
.
args
,
**
task
.
kwargs
)
# the loop keeps ticking
except
Exception
as
e
:
# over
log
.
warning
(
'
Idle task {} crashed - {}: {}
'
.
format
(
else
:
taskName
,
type
(
e
).
__name__
,
str
(
e
)),
exc_info
=
True
)
_idleTimer
.
Start
(
_idleCallRate
,
wx
.
TIMER_ONE_SHOT
)
if
self
.
__allowErrors
:
raise
e
def
inIdle
(
taskName
):
if
task
.
name
is
not
None
:
"""
Returns ``True`` if a task with the given name is queued on the
try
:
self
.
__queueDict
.
pop
(
task
.
name
)
idle loop (or is currently running), ``False`` otherwise.
except
KeyError
:
pass
# More tasks on the queue?
# Request anotherd event
if
self
.
__queue
.
qsize
()
>
queueSizeOffset
:
ev
.
RequestMore
()
# Otherwise use the idle
# timer to make sure that
# the loop keeps ticking
# over
else
:
self
.
__timer
.
Start
(
self
.
__callRate
,
wx
.
TIMER_ONE_SHOT
)
idleLoop
=
IdleLoop
()
"""
A singleton :class:`IdleLoop` instance, created when this module is
imported.
"""
def
idle
(
*
args
,
**
kwargs
):
"""
Equivalent to calling :meth:`IdleLoop.idle` on the ``idleLoop``
singleton.
"""
"""
global
_idleQueueDict
idleLoop
.
idle
(
*
args
,
**
kwargs
)
return
taskName
in
_idleQueueDict
def
idleWhen
(
*
args
,
**
kwargs
):
"""
Equivalent to calling :meth:`IdleLoop.idleWhen` on the ``idleLoop``
singleton.
"""
idleLoop
.
idleWhen
(
*
args
,
**
kwargs
)
@deprecated
(
'
2.7.0
'
,
'
3.0.0
'
,
'
Use idleLoop.inIdle instead
'
)
def
inIdle
(
taskName
):
"""
Deprecated - use ``idleLoop.inIdle`` instead.
"""
return
idleLoop
.
inIdle
(
taskName
)
@deprecated
(
'
2.7.0
'
,
'
3.0.0
'
,
'
Use idleLoop.cancelIdle instead
'
)
def
cancelIdle
(
taskName
):
def
cancelIdle
(
taskName
):
"""
If a task with the given ``taskName`` is in the idle queue, it
"""
Deprecated - use ``idleLoop.cancelIdle`` instead.
"""
is cancelled. If the task is already running, it cannot be cancelled.
return
idleLoop
.
cancelIdle
(
taskName
)
@deprecated
(
'
2.7.0
'
,
'
3.0.0
'
,
'
Use idleLoop.reset instead
'
)
def
idleReset
():
"""
Deprecated - use ``idleLoop.reset`` instead.
"""
return
idleLoop
.
reset
()
@deprecated
(
'
2.7.0
'
,
'
3.0.0
'
,
'
Use idleLoop.callRate instead
'
)
def
getIdleTimeout
():
"""
Deprecated - use ``idleLoop.callRate`` instead.
"""
return
idleLoop
.
callRate
A ``KeyError`` is raised if no task called ``taskName`` exists.
"""
global
_idleQueueDict
@deprecated
(
'
2.7.0
'
,
'
3.0.0
'
,
'
Use idleLoop.callRate instead
'
)
_idleQueueDict
[
taskName
].
timeout
=
-
1
def
setIdleTimeout
(
timeout
=
None
):
"""
Deprecated - use ``idleLoop.callRate`` instead.
"""
idleLoop
.
callRate
=
timeout
def
block
(
secs
,
delta
=
0.01
,
until
=
None
):
def
block
(
secs
,
delta
=
0.01
,
until
=
None
):
...
@@ -428,181 +629,71 @@ def block(secs, delta=0.01, until=None):
...
@@ -428,181 +629,71 @@ def block(secs, delta=0.01, until=None):
break
break
def
idle
(
task
,
*
args
,
**
kwargs
):
def
run
(
task
,
onFinish
=
None
,
onError
=
None
,
name
=
None
):
"""
Run the given task on a ``wx.EVT_IDLE`` event.
"""
Run the given ``task`` in a separate thread.
:arg task: The task to run.
:arg 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 the :func:`inIdle` function.
:arg 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.
:arg 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.
:arg 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.
:arg 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``.
:arg 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.
:arg task: The function to run. Must accept no arguments.
If a ``wx.App`` is not running, the ``timeout``, ``name`` and
:arg onFinish: An optional function to schedule (on the ``wx.MainLoop``,
``skipIfQueued`` arguments are ignored. Instead, the call will sleep for
via :func:`idle`) once the ``task`` has finished.
``after`` seconds, and then the ``task`` is called directly.
:arg onError: An optional function to be called (on the ``wx.MainLoop``,
via :func:`idle`) if the ``task`` raises an error. Passed
the ``Exception`` that was raised.
.. note:: If the ``after`` argument is used, there is no guarantee that
:arg name: An optional name to use for this task in log statements.
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
:returns: A reference to the ``Thread`` that was created.
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
.. note:: If a ``wx`` application is not running, the ``task`` and
expects/accepts its own keyword arguments called ``name``,
``onFinish`` functions will simply be called directly, and
``skipIfQueued``, ``dropIfQueued``, ``after``, ``timeout``, or
the return value will be ``None``.
``alwaysQueue``.
"""
"""
from
fsl.utils.platform
import
platform
as
fslplatform
from
fsl.utils.platform
import
platform
as
fslplatform
global
_idleRegistered
if
name
is
None
:
global
_idleTimer
name
=
getattr
(
task
,
'
__name__
'
,
'
<unknown>
'
)
global
_idleQueue
global
_idleQueueDict
schedtime
=
time
.
time
()
timeout
=
kwargs
.
pop
(
'
timeout
'
,
0
)
after
=
kwargs
.
pop
(
'
after
'
,
0
)
name
=
kwargs
.
pop
(
'
name
'
,
None
)
dropIfQueued
=
kwargs
.
pop
(
'
dropIfQueued
'
,
False
)
skipIfQueued
=
kwargs
.
pop
(
'
skipIfQueued
'
,
False
)
alwaysQueue
=
kwargs
.
pop
(
'
alwaysQueue
'
,
False
)
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
):
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
'
)
app
.
Bind
(
wx
.
EVT_IDLE
,
_wxIdleLoop
)
_idleTimer
=
wx
.
Timer
(
app
)
_idleRegistered
=
True
_idleTimer
.
Bind
(
wx
.
EVT_TIMER
,
_wxIdleLoop
)
if
name
is
not
None
and
inIdle
(
name
):
if
dropIfQueued
:
# The cancelIdle function sets the old
# task timeout to -1, so it won't get
# executed. But the task is left in the
# _idleQueue, and in the _idleQueueDict.
# In the latter, the old task gets
# overwritten with the new task below.
cancelIdle
(
name
)
log
.
debug
(
'
Idle task ({}) is already queued -
'
'
dropping the old task
'
.
format
(
name
))
elif
skipIfQueued
:
log
.
debug
(
'
Idle task ({}) is already queued
'
'
- skipping it
'
.
format
(
name
))
return
log
.
debug
(
'
Scheduling idle task ({}) on wx idle
'
haveWX
=
fslplatform
.
haveGui
'
loop
'
.
format
(
getattr
(
task
,
'
__name__
'
,
'
<unknown>
'
)))
idleTask
=
IdleTask
(
name
,
# Calls the onFinish or onError handler
task
,
def
callback
(
cb
,
*
args
,
**
kwargs
):
schedtime
,
after
,
timeout
,
args
,
kwargs
)
_idleQueue
.
put_nowait
(
idleTask
)
if
cb
is
None
:
return
if
name
is
not
None
:
if
haveWX
:
idle
(
cb
,
*
args
,
**
kwargs
)
_idleQueueDict
[
name
]
=
idleTask
else
:
cb
(
*
args
,
**
kwargs
)
else
:
# Runs the task, and calls
time
.
sleep
(
after
)
# callback functions as needed.
log
.
debug
(
'
Running idle task directly
'
)
def
wrapper
():
task
(
*
args
,
**
kwargs
)
try
:
task
()
log
.
debug
(
'
Task
"
{}
"
finished
'
.
format
(
name
))
callback
(
onFinish
)
def
idleWhen
(
func
,
condition
,
*
args
,
**
kwargs
):
except
Exception
as
e
:
"""
Poll the ``condition`` function periodically, and schedule ``func`` on
:func:`idle` when it returns ``True``.
:arg func: Function to call.
log
.
warning
(
'
Task
"
{}
"
crashed
'
.
format
(
name
),
exc_info
=
True
)
callback
(
onError
,
e
)
:arg condition: Function which returns ``True`` or ``False``. The ``func``
# If WX, run on a thread
function is only called when the ``condition`` function
if
haveWX
:
returns ``True``.
:arg pollTime: Must be passed as a keyword argument. Time (in seconds) to
log
.
debug
(
'
Running task
"
{}
"
on thread
'
.
format
(
name
))
wait between successive calls to ``when``. Defaults to
``0.2``.
"""
pollTime
=
kwargs
.
get
(
'
pollTime
'
,
0.2
)
thread
=
threading
.
Thread
(
target
=
wrapper
)
thread
.
start
()
return
thread
if
not
condition
():
# Otherwise run directly
idle
(
idleWhen
,
func
,
condition
,
after
=
pollTime
,
*
args
,
**
dict
(
kwargs
))
else
:
else
:
kwargs
.
pop
(
'
pollTime
'
,
None
)
log
.
debug
(
'
Running task
"
{}
"
directly
'
.
format
(
name
))
idle
(
func
,
*
args
,
**
kwargs
)
wrapper
()
return
None
def
wait
(
threads
,
task
,
*
args
,
**
kwargs
):
def
wait
(
threads
,
task
,
*
args
,
**
kwargs
):
...
...
This diff is collapsed.
Click to expand it.
fsl/utils/platform.py
+
8
−
1
View file @
0d07fa79
...
@@ -159,7 +159,14 @@ class Platform(notifier.Notifier):
...
@@ -159,7 +159,14 @@ class Platform(notifier.Notifier):
@property
@property
def
haveGui
(
self
):
def
haveGui
(
self
):
"""
``True`` if we are running with a GUI, ``False`` otherwise.
"""
"""
``True`` if we are running with a GUI, ``False`` otherwise.
This currently equates to testing whether a display is available
(see :meth:`canHaveGui`) and whether a ``wx.App`` exists. It
previously also tested whether an event loop was running, but this
is not compatible with execution from IPython/Jupyter notebook, where
the event loop is called periodically, and so is not always running.
"""
try
:
try
:
import
wx
import
wx
app
=
wx
.
GetApp
()
app
=
wx
.
GetApp
()
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment