[wxPython-users] Latest DelayedResult implementation

Josiah Carlson jcarlson at uci.edu
Fri Sep 1 14:42:42 PDT 2006


"Oliver Schoenborn" <oliver.schoenborn at greenley.ca> wrote:
> > -----Original Message-----
> > From: Josiah Carlson [mailto:jcarlson at uci.edu] 
> > Sent: September 1, 2006 12:02 PM
> > ...
> > The three simplest cases I provided in the 
> > 10-minutes-to-write AltDelayedResult.py would likely be 
> > sufficient for the vast majority of users, and are also 
> > *very* simple to understand.
> 
> I would say that AltDelayedResult.py has little advantage over just
> using wx.CallAfter:
> 
> thread.start_new_thread( workerFn, (goo,) )
> def workerFn(cb):
>     result = something
>     wx.CallAfter(cb, result)

You are absolutely correct (except that it offers event posting and
direct calling as alternatives). And my claim is that the vast majority
of people won't find more functionality necessarily worthwhile.  Keep it
simple.  Keep it fool-proof.  There are so many steps in the
construction and use of channels, ports, senders, etc., within
DelayedResult that people are going to get bogged down.  Take, for
example, your "Basic use":

    import DelayedResult, thread
    
    # this gets called when worker thread done: 
    def listener(delayedResult):
        print delayedResult.get()
    
    # create a channel to get result to listener
    channel, port = DelayedResult.byCall(listener)
    
    # create the worker function; you don't have to use decorator if you 
    # don't want, it just eliminates some repetitive stuff:
    @ DelayedResult.autoSend()
    def workerFunc(sender=None):
        # do some stuff
        import time
        time.sleep(3)
        return 'aResult'
    
    # start the worker thread
    sender = channel.getSender()
    thread.start_new_thread( workerFunc, (sender,) )

Here that is using ReturnWithCall...

    import AltDelayedResult

    def listener(result):
        print result

    def workerFunct():
        #do some stuff
        import time
        time.sleep(3)
        return 'aResult'

    AltDelayedResult.ReturnWithCall(listener, workerFunct)

Note that I didn't spend any time decorating to "eliminate some
repetitive stuff", nor did I need to explicitly start a thread, perform
a result.get(), etc.  It all just worked.


> The only advantage of your example module is that it starts the thread
> for the user, something I have not wanted to do so far. However it may
> be a good idea for the novice. 
> 
> > The trick is providing the right amount of functionality 
> > without confusing users.
> 
> I agree completely on that one. 

And yet your module is overly-complicated and confusing.


> In the very first implementation (which I believe I posted), all
> functionality was in one class: DelayedResult.DelayedResult; the concept
> of channel was not explicit so you never created it. The problem with
> that is that it didn't make it clear what to do in what order. The
> separation of the concepts of channel, sender, and result (and I guess
> maybe I could add thread) helps with this since each one has a small
> number of operations available. 

To make people understand something that is difficult to understand, you
don't make it more complicated (with more pieces), you make it easer. 
Simplify, simplify, simplify!

What is the goal?  Run a (long-running) computation outside of the GUI
thread and get the results back (or an exception) via a user-specified
mechanism: wx.CallAfter, wx.PostEvent, direct calling, etc.

How to do it with DelayedResult?  Create a channel, start a new thread
that knows about the channel, have the thread send a message to the
channel.  That seems like quite a bit of work when the alternative is:
ReturnWith*(<stuff>, fcn, *args, **kwargs) .  Abitrarily trivial to
describe, implement, and use.  Anything more just complicates matters.


> Your module hides a _ReturnWithPostEvent which starts a thread, whereas
> DelayedResult chose to leave it up to the user to do that, so naturally
> an extra function is needed. It's pretty obvious that my "extra code" is
> your _ReturnWithPostEvent. 

Exactly one of your lines is the thread starting code, everything else
is mapping what should be reasonable call-return semantic into
abstractions that the vast majority of users will find unnecessary.  You
may find it necessary, but until I hear from someone else who finds all
of that crap will make their application easier to write and understand,
and can prove it by providing source code, I'm going to stick with my
claim. We (wxPython users) aren't going to need it.


> > Remember: some people have difficulty getting the value of a 
> > text control.  You think they are going to get channels?  
> > Thread creation? 
> 
> People learn step by step. One doesn't (voluntarily :) tackle threads
> until confortable with the more basic stuff. As soon as the concept of
> thread enters, all sorts of things must be considered: e.g. has the
> document changed while your result was being computed, such that the
> result is no longer relevant? is the result still needed by the time it
> is available (user may have changed their mind)? etc.

Threads aren't easy.  But when a user is in need (or desire) of a
particular limited set of functionality "I want to run function X
without killing my GUI", there is no reason not to offer the simplest
and easiest mechanism for them to get that functionality.


> The use cases that DelayedResult caters to are not trivial, so I
> wouldn't expect a novice to be able to figure it out without some
> experimentation. 

So "it's supposed to be complicated"?  Bollocks.  Just because something
is not easy doesn't mean you need to make it hard.  Let us take
Richard's recent need to get information from pySerial, process it, then
update the GUI. I believe the mechanism he ended up settling with was
the worker thread + event posting upon document reciept.  He could have
also used the following...

def get_document_and_process():
    #gets the document and processes it
    return ANY_MORE_DOCUMENTS

class dialog(...):
    ...
    def FinishedOne(self, any_more):
        if <any_more is exception>:
            #print exception, etc.
            return
        #update the counter
        if any_more:
            ReturnWithCallAfter(self.FinishedOne,
                                get_document_and_process)

Show me how DelayedResult would have made the above easier.


> So to summarize: you find DelayedResult more complex than necessary
> (complexity is not useful since if more than the simple case is needed
> then what DelayedResult provides will be insufficient), and that
> DelayedResult is more complex than most people would be able to handle.
> Those are all subjective statements. For me, the ability of
> DelayedResult to support encapsulation (which is not unrelated to
> chaining) is a requirement, not just a "nice to have" so the simpler
> approach you provide is not useful. And I figure, if I needed
> DelayedResult, perhaps someone else will.

In the last 4 years of using wxPython, I have used two of the three
patterns I described (I haven't really used direct calling, but
queue.put could be an application), and a fourth that I have not
described in this thread: worker thread + queue. Beyond that, I have had
no use for chaining, channels, etc., and I have read no posts in
wxpython-users that would suggest that such functionality would have
been made writing applications better or easier through their use.


> The things I get out of this discussion are good (IMO): 1) I should add
> a helper that makes it unnecessary to create thread explicitely (while
> allowing user to choose which thread module to use, IF default not
> adequate); 2) I should explain the basic concepts better; 3) I should
> move the testing to a separate file so that module can be used in py 2.3
> too; 4) Any other suggestions welcome. 

The threading module offers all of the functionality of the thread
module plus more.  Use the threading module.  There is no reason not to
use threading, and a few not to use thread.

 - Josiah





More information about the wxpython-users mailing list