Beispiel #1
0
 def __init__(self, title):
     self._title = title
     self._subtitle = 'Queued'
     self.scheduling_style = SCHEDULING_STYLE_NONE
     self._children = []
     self._num_children_complete = 0
     self._complete = False
     self.listeners = []
     
     self._did_yield_self = False            # used by leaf tasks
     self._future = Future()                 # used by leaf tasks
     # TODO: Consider merging the following two fields
     self._first_incomplete_child_index = 0  # used by SCHEDULING_STYLE_SEQUENTIAL
     self._next_child_index = 0              # used by SCHEDULING_STYLE_ROUND_ROBIN
Beispiel #2
0
class Task(object):
    """
    Encapsulates a long-running process that reports its status occasionally.
    A task may depend on the results of a child task during its execution.
    
    Generally there are two kinds of tasks:
    (1) Leaf tasks
        - Performs a single long-running operation on a background thread
          and completes immediately after this operation is complete.
            - The operation is executed by the __call__() method,
              which must be implemented by leaf task subclasses.
    (2) Container tasks
        - Uses child tasks to perform all its work.
            - Should set the 'scheduling_style' property in its constructor.
            - Should add the initial set of children in its constructor.
        - May add additional children tasks over time to perform additional work.
            - Generally this is done upon the completion of a child task.
        - Automatically listen to child tasks. A container task may override:
            o child_task_subtitle_did_change
            o child_task_did_complete
    
    Tasks must generally be manipulated on the foreground thread unless
    documented otherwise.
    """
    
    def __init__(self, title):
        self._title = title
        self._subtitle = 'Queued'
        self.scheduling_style = SCHEDULING_STYLE_NONE
        self._children = []
        self._num_children_complete = 0
        self._complete = False
        self.listeners = []
        
        self._did_yield_self = False            # used by leaf tasks
        self._future = Future()                 # used by leaf tasks
        # TODO: Consider merging the following two fields
        self._first_incomplete_child_index = 0  # used by SCHEDULING_STYLE_SEQUENTIAL
        self._next_child_index = 0              # used by SCHEDULING_STYLE_ROUND_ROBIN
    
    # === Properties ===
    
    @property
    def title(self):
        """
        The title of this task. Fixed upon initialization.
        """
        return self._title
    
    def _get_subtitle(self):
        """
        The subtitle for this task.
        The setter (but not the getter) is threadsafe.
        """
        return self._subtitle
    def _set_subtitle(self, value):
        def fg_task():
            #print '%s -> %s' % (self, value)
            self._subtitle = value
            
            for lis in self.listeners:
                if hasattr(lis, 'task_subtitle_did_change'):
                    lis.task_subtitle_did_change(self)
        fg_call_later(fg_task)
    subtitle = property(_get_subtitle, _set_subtitle)
    
    @property
    def children(self):
        return self._children
    
    @property
    def num_children_complete(self):
        return self._num_children_complete
    
    @property
    def complete(self):
        """
        Whether this task is complete.
        """
        return self._complete
    
    @property
    def future(self):
        """
        Returns a Future that receives the result of this task.
        
        This property is only defined by default for leaf tasks.
        Container tasks may optionally override this if they
        conceptually return a value.
        """
        if callable(self):
            return self._future
        else:
            raise ValueError('Container tasks do not define a result by default.')
    
    # === Protected Operations ===
    
    def append_child(self, child):
        self._children.append(child)
        child.listeners.append(self)
        
        for lis in self.listeners:
            if hasattr(lis, 'task_did_append_child'):
                lis.task_did_append_child(self, child)
    
    def finish(self):
        """
        Marks this task as completed.
        Threadsafe.
        """
        def fg_task():
            self._complete = True
            self.subtitle = 'Complete'
            
            # NOTE: Making a copy of the listener list since it is likely to be modified by callees.
            for lis in list(self.listeners):
                if hasattr(lis, 'task_did_complete'):
                    lis.task_did_complete(self)
        fg_call_later(fg_task)
    
    def clear_children(self):
        """
        Clears all of this task's children.
        Recommended only for use by RootTask.
        """
        if not all(c.complete for c in self.children):
            raise ValueError('Some children are not complete.')
        
        self._children = []
        self._first_incomplete_child_index = 0
        self._next_child_index = 0
        
        for lis in self.listeners:
            if hasattr(lis, 'task_did_clear_children'):
                lis.task_did_clear_children(self)
    
    # === Public Operations ===
    
    def try_get_next_task_unit(self):
        """
        Returns a callable ("task unit") that completes a unit of work for
        this task, or None if no more units can be provided until at least
        one of the previously returned units completes.
        
        Task units may be run on any thread.
        
        If this is a leaf task, its own __call__() method will be returned
        as the solitary task unit. As a task unit, it must be designed to
        run on any thread.
        """
        
        if self.complete:
            return None
        
        if callable(self):
            if not self._did_yield_self:
                self._did_yield_self = True
                return self._call_self_and_record_result
            else:
                return None
        else:
            if len(self.children) == 0:
                raise ValueError('Container task has no children tasks.')
            
            if self.scheduling_style == SCHEDULING_STYLE_NONE:
                raise ValueError('Container task has not specified a scheduling style.')
            elif self.scheduling_style == SCHEDULING_STYLE_SEQUENTIAL:
                while self._first_incomplete_child_index < len(self.children):
                    if self.children[self._first_incomplete_child_index].complete:
                        self._first_incomplete_child_index += 1
                    else:
                        cur_child_index = self._first_incomplete_child_index
                        while cur_child_index < len(self.children):
                            unit = self.children[cur_child_index].try_get_next_task_unit()
                            if unit is not None:
                                return unit
                            cur_child_index += 1
                        return None
                return None
            elif self.scheduling_style == SCHEDULING_STYLE_ROUND_ROBIN:
                cur_child_index = self._next_child_index
                while True:
                    unit = self.children[cur_child_index].try_get_next_task_unit()
                    if unit is not None:
                        self._next_child_index = (cur_child_index + 1) % len(self.children)
                        return unit
                    cur_child_index = (cur_child_index + 1) % len(self.children)
                    if cur_child_index == self._next_child_index:
                        # Wrapped around and back to where we started without finding anything to do
                        return None
            else:
                raise ValueError('Container task has an unknown scheduling style (%s).' % self.scheduling_style)
    
    def _call_self_and_record_result(self):
        # (Ignore client requests to cancel)
        self._future.set_running_or_notify_cancel()
        try:
            self._future.set_result(self())
        except:
            (_, e, _) = sys.exc_info()
            self._future.set_exception(e)
        finally:
            self.finish()
    
    # === Internal Events ===
    
    def task_subtitle_did_change(self, task):
        if hasattr(self, 'child_task_subtitle_did_change'):
            self.child_task_subtitle_did_change(task)
    
    def task_did_complete(self, task):
        self._num_children_complete += 1
        
        if hasattr(self, 'child_task_did_complete'):
            self.child_task_did_complete(task)
        
        task.listeners.remove(self)