Designing a cancellable function

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Leo Breebaart

    #1

    Designing a cancellable function

    I have written a function foo() that iterates over and processes
    a large number of files. The function should be available to the
    user as library function, via a command-line interface, and
    through a GUI.

    So, I added a 'config' object as a parameter to foo() that can be
    used by the caller to explicitly pass in user-defined settings.
    Because the processing foo() does can take such a long time, the
    next thing I did was add an 'update_functio n' callback parameter
    that foo() will call regularly, so that the GUI can update a
    progress bar, and the command-line version can print dots, etc.

    I now would also like to add the possibility to allow the user to
    *cancel* the execution of foo() during the processing, and I am
    wondering what the best / most Pythonic way to design this is.

    One obvious approach seems to me to turn the 'config' object into
    something more dynamic, and have foo() regularly inspect it to
    see if somebody in the main thread has set e.g. config.abort to
    True.

    Another approach would be to turn foo() into a proper (threaded)
    class with distinct run() and abort() methods. But the caller
    would still need to register the update callback somehow, and I
    am wondering if this way the whole API for foo() won't become to
    complex and overdesigned.

    I was wondering if anybody has any insights or best practice
    recommendations for me here. Do I keep the function interface? Do
    I use a class? Any other solution I am overlooking?

    Many thanks in advance for your advice.

    --
    Leo Breebaart <leo@lspace.org >
  • Michele

    #2
    Re: Designing a cancellable function

    Hi Leo,

    what about one (or more) thread(s) which run the eat() method of
    FilesEater object with inheriths from a superclass what's needed to
    update the progress attribute (for example a _inc_progress() private
    method)? (So you can have a UrlEater class and so on, them all knowing
    how to update their progress)
    Then you give FilesEater (or maybe a better name) a reference to a
    config dict at init time.

    You can have multiple FilesEater with different configurations and you
    can handle the problem of stopping execution with a stopEating()
    public method which sets an internal flag (a Lock object) and you
    check it inside the object runloop with something like:

    def eat(self):
    for f in self.files:
    do_some_stuff()
    self._eat_for_r eal()
    do_some_other_s tuff()

    def _eat_for_real() : # you should use the with statement inside here =)
    self._eatLock.a cquire()
    do_the_processi ng
    _write_the_resu lt() # !
    self._eatLock.r elease()

    with this you can pause and restart the execution by calling
    pause/restart methods that acquire and release the eatLock. It writes
    the result before releasing it so you know it finishes the last chunk
    when you suspend it.
    If then you want to abord, just call the pause() on every *Eater
    object and exit.
    If you want to exit immediately just exit and design the code that
    reads the output of the eat() processing in a way that it can
    recognize broken chunks, delete them and go on (if they are written
    on-disk obviously).

    Hope this helps,
    Michele


    On 16 Dec 2006 01:20:47 GMT, Leo Breebaart <leo@lspace.org wrote:
    I have written a function foo() that iterates over and processes
    a large number of files. The function should be available to the
    user as library function, via a command-line interface, and
    through a GUI.
    >
    So, I added a 'config' object as a parameter to foo() that can be
    used by the caller to explicitly pass in user-defined settings.
    Because the processing foo() does can take such a long time, the
    next thing I did was add an 'update_functio n' callback parameter
    that foo() will call regularly, so that the GUI can update a
    progress bar, and the command-line version can print dots, etc.
    >
    I now would also like to add the possibility to allow the user to
    *cancel* the execution of foo() during the processing, and I am
    wondering what the best / most Pythonic way to design this is.
    >
    One obvious approach seems to me to turn the 'config' object into
    something more dynamic, and have foo() regularly inspect it to
    see if somebody in the main thread has set e.g. config.abort to
    True.
    >
    Another approach would be to turn foo() into a proper (threaded)
    class with distinct run() and abort() methods. But the caller
    would still need to register the update callback somehow, and I
    am wondering if this way the whole API for foo() won't become to
    complex and overdesigned.
    >
    I was wondering if anybody has any insights or best practice
    recommendations for me here. Do I keep the function interface? Do
    I use a class? Any other solution I am overlooking?
    >
    Many thanks in advance for your advice.
    >
    --
    Leo Breebaart <leo@lspace.org >
    --

    >

    Comment

    • Gabriel Genellina

      #3
      Re: Designing a cancellable function

      At Friday 15/12/2006 22:20, Leo Breebaart wrote:
      >I have written a function foo() that iterates over and processes
      >a large number of files. The function should be available to the
      >user as library function, via a command-line interface, and
      >through a GUI.
      >
      >So, I added a 'config' object as a parameter to foo() that can be
      >used by the caller to explicitly pass in user-defined settings.
      >Because the processing foo() does can take such a long time, the
      >next thing I did was add an 'update_functio n' callback parameter
      >that foo() will call regularly, so that the GUI can update a
      >progress bar, and the command-line version can print dots, etc.
      >
      >I now would also like to add the possibility to allow the user to
      >*cancel* the execution of foo() during the processing, and I am
      >wondering what the best / most Pythonic way to design this is.
      I can't say if this is the "best/more Pythonic way", but a simple way
      would be to use the return value from your callback. Consider it an
      "abort" function: if it returns True, cancel execution; as long as it
      returns False, keep going.
      (The somewhat "reversed" meaning is useful in case the user doesn't
      provide a callback at all, or it's an empty one, or it contains just
      a print ".", statement, all of these returning False; so, to actually
      abort the process it must have an explicit "return True" statement).


      --
      Gabriel Genellina
      Softlab SRL

      _______________ _______________ _______________ _____
      Correo Yahoo!
      Espacio para todos tus mensajes, antivirus y antispam ¡gratis!
      ¡Abrí tu cuenta ya! - http://correo.yahoo.com.ar

      Comment

      • Giovanni Bajo

        #4
        Re: Designing a cancellable function

        Gabriel Genellina wrote:
        >I now would also like to add the possibility to allow the user to
        >*cancel* the execution of foo() during the processing, and I am
        >wondering what the best / most Pythonic way to design this is.
        >
        I can't say if this is the "best/more Pythonic way", but a simple way
        would be to use the return value from your callback. Consider it an
        "abort" function: if it returns True, cancel execution; as long as it
        returns False, keep going.
        It's even easier if the callback function simply raise an exception, which can
        be caught from the outside:


        class AbortOperationE rror(RuntimeErr or):
        pass


        def callback(N):
        updateProgressB ar(N)
        processGUIEvent s()
        if exit_button_pre ssed:
        raise AbortOperationE rror()


        try:
        longFunction(ca llback)
        except AbortOperationE rror()
        pass


        def longFunction(ca llback):
        for i in xrange(10000000 00):
        # do something
        callback(i / 1000000000.0)


        --
        Giovanni Bajo

        Comment

        Working...