Introduction¶
In this guide we’ll introduce the key players in the traits_futures
package. All classes and data items mentioned here can be imported directly
from the traits_futures.api
module.
Submitting background tasks¶
The TraitsExecutor
is the main point of entry to traits_futures
. Its job is
to accept one or more task submissions. For each task submitted, it sends the
computation to run in the background on a worker from a worker pool, and
returns a corresponding “future” object that allows monitoring of the state of
the background computation and retrieval of its results.
We’ll examine the future objects in the next section. This section deals with the executor’s main top-level methods and the task submission functions.
To submit a task, use one of the convenience submission functions available
from traits_futures.api
:
The
submit_call
function allows submission of a simple Python callable, with given positional and named arguments. For example:submit_call(my_executor, int, "10101", base=2)
will execute
int("10101", base=2)
in the background.submit_call
doesn’t wait for the background task to finish; instead, it immediately returns aCallFuture
object. See the next section for more details on theCallFuture
and related objects.The
submit_iteration
function allows submission of an arbitrary iterable. The user provides a callable which, when called, returns an iterable object. For example:submit_iteration(my_executor, range, 0, 5)
It returns an
IterationFuture
object.The
submit_progress
function allows submission of a progress-reporting callable, and returns aProgressFuture
object. The callable submitted must have a parameter called “progress”. A value for this parameter will be passed (by name) by the executor machinery. The value passed for the “progress” parameter can then be called to send progress reports to the associatedProgressFuture
object. If the future has been cancelled, the next call toprogress
in the background task will raise aTaskCancelled
exception.For example, your callable might look like this:
def interruptible_sum_of_squares(n, progress): """ Compute the sum of squares of integers smaller than n.""" total = 0 for i in range(n): # Send a pair of the form (steps_completed, total_steps) progress((i, n)) total += i*i progress((n, n))
The computation consists of
n
steps: a progress report is sent before each step, and after the end of the computation. Theprogress
callable accepts a single Python object, but of course that Python object can be a compound object like atuple
or adict
. It’s up to you to choose the format of the objects you want to send. They’ll arrive in exactly the same format in theProgressFuture
, and then your application can choose how to interpret them.
In the current version of Traits Futures, tasks may only be submitted from the
main thread. An attempt to submit a task from a background thread will raise
RuntimeError
. This restriction may be removed in the future.
Working with future objects¶
The various submission methods described above are asynchronous: they return a “future” object immediately without waiting for the background task to complete. The returned “future” has three purposes:
it provides information about the current state of the background task
it provides results from the background task (or exception information in the case of failure)
it provides a way to request that a background task be cancelled
In this section we describe these three topics in more detail.
Future states¶
The CallFuture
, IterationFuture
and ProgressFuture
objects all provide a
state
trait, of trait type FutureState
, that represents the state of
the underlying computation. That state has one of six possible different
values:
WAITING
The background task has been scheduled to run, but has not yet started executing (for example, because the worker pool is still busy dealing with previously-submitted tasks.
EXECUTING
The background task is currently executing on one of the workers.
COMPLETED
The background task has completed without error. For a progress task or a simple call, this implies that a result has been returned and is available via the
result
property of the future. For an iteration, it means that the iteration has completed.FAILED
The background task raised an exception at some point in its execution. Information about the exception is available via the
exception
property of the future.CANCELLING
Cancellation of the background task has been requested, but the background task has not yet acknowledged that request.
CANCELLED
The task has stopped following a cancellation request.
In addition, there are two traits whose values are derived from the
state
trait: the done
trait is True
when state
is one
of COMPLETED
, FAILED
or CANCELLED
, and the cancellable
trait is
True
when state
is one of WAITING
or EXECUTING
.
It’s important to understand that the state
trait represents the state
of the background task to the best of knowledge of the main thread. For
example, when the background task starts executing, it sends a message to the
corresponding future telling it to change its state from WAITING
to
EXECUTING
. However, that message won’t necessarily get processed immediately,
so there will be a brief interval during which the background task has, in
fact, started executing, but the state of the future is still WAITING
.
Here’s a diagram showing the possible state transitions. The initial state
is WAITING
. The final states are CANCELLED
, COMPLETED
and FAILED
.
The future expects to receive either the message sequence ["started",
"raised"]
or the message sequence ["started", "returned"]
from the
background task: this happens even if cancellation is requested.
Getting task results¶
Background task results can be retrieved directly from the corresponding futures.
The submit_call
and submit_progress
functions run callables that eventually
expect to return a result. Once the state of the corresponding future reaches
COMPLETED
, the result of the call is available via the result
attribute.
Assuming that your calculation future is stored in a trait called future
,
you might use this as follows:
@observe('future:done')
def _update_result(self, event):
future = event.object
self.my_results.append(future.result)
Any attempt to access the future’s result
before the future completes
successfully will raise an AttributeError
. This includes the cases where
the background task was cancelled, or failed with an exception, as well
as the cases where the task is still executing or has yet to start running.
A ProgressFuture
object also receives progress information send by the
background task via its progress
event trait. You might use that
trait like this:
@observe('future:progress')
def _report_progress(self, event):
progress_info = event.new
current_step, max_steps, matches = progress_info
self.message = "{} of {} chunks processed. {} matches so far".format(
current_step, max_steps, matches)
The submit_iteration
function is a little bit different: it produces a result
on each iteration, but doesn’t necessarily give a final result. Its
result_event
is an traits.trait_types.Event
trait that you can hook
listeners up to in order to receive the iteration results. For example:
@observe('future:result_event')
def _record_result(self, event):
result = event.new
self.results.append(result)
self.update_plot_data()
If a background task fails with an exception, then the corresponding future
eventually reaches FAILED
state. In that case, information about the
exception that occurred is available in the future’s exception
attribute.
This information takes the form of a tuple of length 3, containing stringified
versions of the exception type, the exception value and the exception
traceback.
As with result
, an attempt to access exception
for a future that’s not in
FAILED
state will give an AttributeError
.
Cancelling the background task¶
The CallFuture
, IterationFuture
and ProgressFuture
classes all have a
cancel
method that allows the user to request cancellation of the
corresponding background task. That request gets interpreted a little
differently depending on the type of task.
For CallFuture
, the cancel
method either tells a waiting task
not to execute, or tells an already executing task that the user
is no longer interested in the result. It doesn’t interrupt an
already executing background task.
For IterationFuture
, the cancel
method causes a running
background task to abort on the next iteration. No further results
are received after calling cancel
.
For ProgressFuture
, the cancel
method causes a running
task to abort the next time that task calls progress
. No further
progress results are received after calling cancel
.
In all cases, a task may only be cancelled if the state of the associated
future is either WAITING
or EXECUTING
. When cancel
is called on a future
in one of these two states, the future’s state is changed to CANCELLING
,
a cancellation request is sent to the associated task, and the call returns
True
. When cancel
is called on a future in another state, the call has
no effect, and returns False
.
A successful cancel
immediately puts the future into CANCELLING
state, and
the state is updated to CANCELLED
once the future has finished executing. No
results or exception information are received from a future in CANCELLING
state. A cancelled future will never reach FAILED
state, and will never
record information from a background task exception that occurs after the
cancel
call.
Stopping the executor¶
To avoid unexpected side-effects during Python process finalization, it’s
recommended to shut down a running TraitsExecutor
explicitly prior to process
exit. Similarly, when writing a unit test that makes use of a TraitsExecutor
,
that executor should be shut down at test exit, to avoid potential for
unexpected interactions with other tests.
This section describes the two methods available for executor shutdown:
shutdown
and stop
.
Executor states¶
Like the various future classes, a TraitsExecutor
also has a state
trait, of type ExecutorState
. This state is one of the following:
RUNNING
The executor is running and accepting task submissions. This is the state of a newly-created executor.
STOPPING
Shutdown has been initiated or partially completed, but there are still running background tasks associated with this executor. An executor in
STOPPING
state will not accept new task submissions.STOPPED
The executor has stopped, all resources associated with the executor have been released, and all background tasks associated with this executor have finished. An executor in
STOPPED
state will not accept new task submissions, and cannot be restarted.
Executor shutdown¶
Once a TraitsExecutor
object is no longer needed (for example at application
shutdown time), it can be shut down via its shutdown
method. This method is
blocking: it waits for all of the background tasks to complete before
returning. In more detail, if called on a running executor, the shutdown
method performs the following tasks, in order:
Moves the executor to
STOPPING
state.Requests cancellation of all waiting or executing background tasks.
Waits for all background tasks to complete.
Shuts down the worker pool (if that worker pool is owned by the executor).
Moves the executor to
STOPPED
state.
If called on an executor in STOPPED
state, shutdown
simply returns
without taking any action. If called on an executor in STOPPING
state,
any of the above actions that have not already been taken will be taken.
Shutdown with a timeout¶
To avoid blocking indefinitely, the shutdown
method also accepts a
timeout
parameter. That timeout is used when waiting for the background
tasks to complete. If the background tasks fail to complete within the given
timeout, shutdown
will raise RuntimeError
and leave the executor in
STOPPING
state. The worker pool used by the executor will not have been shut
down.
Non-blocking executor shutdown¶
Occasionally, it may be desirable to shut down an executor during normal
application execution, rather than at application shutdown time. In this
situation calling shutdown
is problematic, since that method is blocking and
so will make the GUI unresponsive. Instead, users can call the non-blocking
stop
method. This method:
Moves the executor to
STOPPING
state.Requests cancellation of all waiting or executing background tasks.
Typically, the event loop will continue to run after calling the stop
method.
Under that running event loop, all futures will eventually reach one of the
final states (COMPLETED
, FAILED
or CANCELLED
). When that happens, the
system automatically:
Shuts down the worker pool (if that worker pool is owned by the executor).
Moves the executor to
STOPPED
state.
If there are no waiting or executing background tasks, then stop
goes
through all of the steps above at once, moving the executor through
the STOPPING
state to STOPPED
state.
Note that while stop
can only be called on an executor in RUNNING
state,
it’s always legal to call shutdown
on an executor, regardless of the current
state of that executor. In particular, calling shutdown
after stop
is
permissible, but calling stop
after shutdown
would be an error.