Traits Futures: reactive background processing for Traits and TraitsUI

Release v0.1.1.

The traits_futures package provides a means to fire off a background calculation from a TraitsUI application, and later respond to the result(s) of that calculation, leaving the main UI responsive for user interactions while the background calculation is in progress.

Features

  • Supports simple calls, iterations, and progress-reporting functions. Can easily be extended to support other messaging patterns.
  • Dispatching a background task returns a “future” object, which provides:
    • information about state changes (e.g., background task completion)
    • facilities to (cooperatively) cancel a long-running background task
    • access to result(s) arriving from the background task
  • Future objects are HasTraits instances, suitable for integration into a TraitsUI application.
  • No need to be a threading expert! Incoming results arrive as trait changes in the main thread. This eliminates a large class of potential issues with traditional thread-based solutions (race conditions, deadlocks, and UI updates off the main thread).
  • Cross-platform, and compatible with both Python 2 and Python 3.

Limitations

  • By design, and unlike concurrent_futures, traits_futures requires the UI event loop to be running in order to process results.
  • For the moment, traits_futures requires Qt. Support for wxPython may arrive in a future release.
  • No multiprocessing support yet. Maybe one day.

Quick start

Here’s a complete example showing a minimal TraitsUI application that fires off a background computation when its “Calculate” button is pressed, and shows the result when it arrives.

# (C) Copyright 2018-2019 Enthought, Inc., Austin, TX
# All rights reserved.

import time

from traits.api import Button, HasTraits, Instance, Int, on_trait_change, Str
from traitsui.api import Item, View

from traits_futures.api import CallFuture, TraitsExecutor


# The target function that we plan to execute in the background.
def slow_square(n):
    """ Square the given input, slowly. """
    time.sleep(2.0)
    return n * n


class QuickStartExample(HasTraits):
    #: The executor to submit tasks to.
    executor = Instance(TraitsExecutor, ())

    #: The future object returned on task submission.
    future = Instance(CallFuture)

    #: Input for the calculation.
    input = Int(10)

    #: Result, in string form.
    result = Str("No result yet")

    #: Button to start the calculation.
    calculate = Button()

    @on_trait_change("calculate")
    def _submit_background_call(self):
        # Returns immediately.
        self.future = self.executor.submit_call(slow_square, self.input)

    @on_trait_change("future:done")
    def _report_result(self, future, name, done):
        self.result = "Square is {}".format(future.result)

    traits_view = View(
        Item("input"),
        Item("result", style="readonly"),
        Item("calculate", enabled_when="future is None or future.done"),
    )


QuickStartExample().configure_traits()

Indices and tables