The cornerstone of Serpentine is an operation that is an asynchronous code execution that can be started and that eventually finishes. Operations are observable; when an operation finishes its listeners are warned.
class MyListener(object):
def on_finished(self, evt):
print evt.source, 'has finished', evt.is_successful
op = SomeOperation()
my_listener = MyListener()
op.add_listener(my_listener)
op.remove_listener(other_listener)
Operations can terminate in three different states: successful, aborted, and error. There is an attribute, in the finished event object, for each state (e.g., is_aborted).
Operations can be composite with the help of OperationQueues. These are used throughout the code to create complex operations that are well separated in possibly reusable parts. This is an example of the usage of an operation queue:
queue = OperationQueue([op1, op2]) queue.append(op3) queue.insert(0, op4) assert len(queue) == 4 queue.start() # wait a bit... queue.stop()
Queues also trigger the event before_operation_starts. Listeners that implement a method with that name are called with two arguments the event and the operation getting started. Notice that objects wanting to listen to that event must also use the method queue.add_listener. Listeners that do not implement the method with the name of the event being triggered are simply skipped. Exceptions raised by listener's methods do not interfere with the dispatching.
When implementing an operation there are some things you must extend: the attribute is_running and the method start, which should start operations in a new thread or in a gobject.idle_add.
This is the code listing of a simple class of serpentine.operations:
class CallableOperation(Operation):
can_stop = False
running = False
def __init__ (self, func):
super(CallableOperation, self).__init__()
self.func = func
def start (self):
gobject.idle_add(self._send)
self.running = True
def _send(self):
try:
self.func()
self.running = False
self._send_finished_event(SUCCESSFUL)
except Exception, err:
self.running = False
self._send_finished_event(ERROR, err)
The helper method _send_finished_event notifies listeners with a FinishedEvent, which must be issued when an operation finishes. This class turns a function into an operation. This operation should not block for a long time or it will freeze the UI, remember it is being called in Gtk+'s main loop (through gobject.idle_add).
Another type of operation is a MeasurableOperation. Extending classes must implement the attribute progress. An OperationQueue is a MeasurableOperation. Instances of MeasurableOperation are well integrated with a generic progress window that exists in the module serpentine.gtkutil. For example, when the user clicks on the write button, an operation ConvertAndWrite is created. This operation, amongst other things, is a queue of two operations: FetchMusicList and WriteAudioDisc, each of which a composite of other operations. The structure (e.g., the children) of the operation ConvertAndWrite may change during time, for example if we add normalization to the process, but the progress window and the other parts of code that interact with ConvertAndWrite are not affected by this change.
2 comments:
This is nice. Seems almost like a Twisted Deferred, and probably could implement it as that quite easily.
I can see how this is useful in a task-based application.
I think I am going to implement a frankenstein version of this and pida.utils.gthreads using twisted Deferreds.
Yes, it's really similar. Unfortunately I didn't know it by then. Would've saved me alot of work ;)
Post a Comment