interest in DelayedResult?

I have created a module called DelayedResult.py (still prototype, but
works) that helps use multithread in GUI apps, something many novice
users are not comfortable with. It's basic use case is:

- you have a task, triggered by a GUI event, that takes more than
  1/10th sec to execute, so you loose GUI response in that time
- starting the task from the event handler actually involves going
  through one or more layers with pre-processing at each level; e.g.
  handler calls slowTask, which does some setup stuff then itself
  calls lowerLevelSlowTask, which starts the thread and returns
immediately
- when the task is done, the result is new data that must be pushed to
the GUI
- once the result is available, each (or at least one) level needs to
  do some post-processing before the final result can be sent to GUI

DelayedResult makes it really easy to get the final result back from the
thread to the GUI, without having to know anything about creating new
event types or CallAfter etc. It manages the reverse chain of "callers",
makes it a lot easier to transform a single thread impl to a
multi-thread implementation, and makes it easy to see what is pre- and
post-processing of the slow task. It also limits the amount of
synchronization code needed. You need WxAppTester.py to run the module
test.

Example (pseudo code):

# Single threaded case:

handle_download(self, event):
   # get file from web server:
   fileObj = serverProxy.get_file( theFile ) # blocks/takes long
   ask_user_where_to_save() # post-processing
   update_tree_view()

class ServerProxy:
   ...
   get_file(self, theFile):
       ... pre-processing, e.g. add complete path to theFile ...
       fileObj = NetServices.get_file( theFilePath ) # blocks/takes long
       ... post-processing using fileObj ...
       return fileObj

class NetServices:
    get_file(self, filePath):
       ... pre-processing ...
       ... use urlopen etc... blocks here until got it ...
       return fileObj

# Transform for two threaded case using DelayedResult. Note how
# the structure is the same but post- stuff are now split into
# separate functions:

class YourController:
    handle_download(self, event):
        # get file from web server:
        delayedResult = DelayedResult( self.__haveNewResult )
        # now returns "immediately" with nothing:
        serverProxy.get_file( theFile, delayedResult )

    __haveNewResult(self, delayedResult):
        fileObj = delayedResult.getResult()
        ask_user_where_to_save() # post-processing
        update_tree_view()

class ServerProxy:
    ...
    get_file(self, theFile, delayedResult):
        ... pre-processing, e.g. add complete path to theFile ...
        delayedResult.prepend( self.__haveNewResult )
        # now returns "immediately" with nothing:
        NetServices.get_file( theFilePath, delayedResult )

    __haveNewResult(self, delayedResult):
        something = delayedResult.getResult()
        ... post-processing using something ...
        return fileObj

class NetServices:
    ...
    get_file(self, filePath, delayedResult):
        ... pre-processing ...
        def threadedFn(delayedResult):
            fileObj = urlopen(...) # blocks/takes long
            delayedResult.sendResult(fileObj)
        thread.start_new(threadedFn, (delayedResult,))

Would it make sense to have it in wx.lib?

Oliver

DelayedResult.py (4.52 KB)

WxAppTester.py (3.18 KB)

Oliver Schoenborn wrote:

I have created a module called DelayedResult.py (still prototype, but
works) that helps use multithread in GUI apps, something many novice
users are not comfortable with. It's basic use case is:

- you have a task, triggered by a GUI event, that takes more than 1/10th sec to execute, so you loose GUI response in that time
- starting the task from the event handler actually involves going through one or more layers with pre-processing at each level; e.g. handler calls slowTask, which does some setup stuff then itself calls lowerLevelSlowTask, which starts the thread and returns
immediately
- when the task is done, the result is new data that must be pushed to
the GUI - once the result is available, each (or at least one) level needs to do some post-processing before the final result can be sent to GUI

DelayedResult makes it really easy to get the final result back from the
thread to the GUI, without having to know anything about creating new
event types or CallAfter etc. It manages the reverse chain of "callers",
makes it a lot easier to transform a single thread impl to a
multi-thread implementation, and makes it easy to see what is pre- and
post-processing of the slow task. It also limits the amount of
synchronization code needed. You need WxAppTester.py to run the module
test.

Would it make sense to have it in wx.lib?

Yes, I'm not sure I follow all of your explanation but I think that the concept is a good one.

BTW, the test code gets some exceptions:

$ py DelayedResult.py
Slow function started
Slow function done
Unhandled exception in thread started by <function slowFn at 0xb7d53064>
Traceback (most recent call last):
   File "DelayedResult.py", line 117, in slowFn
     cb.sendResult((5,'a'))
   File "DelayedResult.py", line 66, in sendResult
     print "Posting result (%s) to %s" % (self.peekResult(), fn0)
AttributeError: DelayedResult instance has no attribute 'peekResult'
Slow function started
Slow function done
Unhandled exception in thread started by <function slowFn at 0xb7d53064>
Traceback (most recent call last):
   File "DelayedResult.py", line 117, in slowFn
     cb.sendResult((5,'a'))
   File "DelayedResult.py", line 63, in sendResult
     fn0(self)
TypeError: __call__() takes exactly 3 arguments (2 given)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!