after, after_cancel and Python 2.3

Discussion in 'Python' started by Edward K. Ream, Aug 29, 2003.

  1. Previous to Python 2.3 my app has destroyed the root Tk window using
    root.destroy rather than the more usual root.quit. In Python 2.3 this does
    not work so well. In some situations (i.e., for some data), Tk (not
    Tkinter) complains that an "after" routine does not exist that has been
    registered with the Tkinter after routine. Moreover, in Python 2.3, the
    tkinter after_cancel routine does not appear to work at all, regardless of
    whether root.destroy() or root.quit() follows after_cancel.

    When using root.quit(), the after routine is called properly, so no real
    harm is done. When using root.destroy(), it appears that Tkinter (or is it
    Tk) attempts to call the "after" routine after the "after" routine has
    already been destroyed.

    For example, the following code appears in my app's shutdown code:

    self.killed = true # Disable after events.

    # New for Python 2.3: attempt to cancel the after handler.
    if self.afterHandler != None:
    print "finishQuit: canceling",self.afterHandler
    self.root.after_cancel(self.afterHandler)
    self.afterHandler = None

    if 1: # Works in Python 2.1 and 2.2. Leaves Python window open.
    self.root.destroy()

    else: # Works in Python 2.3. Closes Python window.
    self.root.quit()

    In versions before Python 2.3 there was never any need for a call to
    root.after_cancel and the call to root.destroy never complained. In Python
    2.3, the call to root.destroy() sometimes gives an error message like this:

    finishQuit: canceling after#18
    >>> invalid command name "14189192callit"

    while executing
    "14189192callit"
    ("after" script)

    If my code calls self.root.quit() what happens is that the "after" routine
    is called, sees self.killed == true and returns. In other words, the
    self.killed hack allows the after routine to work around the fact that
    after_cancel has had no apparent effect.

    Can anyone see any obvious problem in my code?

    Here are the definitions of after and after_cancel in tkinter.py. This code
    has not changed between Python 2.2 and Python 2.3.

    def after(self, ms, func=None, *args):
    """Call function once after given time.

    MS specifies the time in milliseconds. FUNC gives the
    function which shall be called. Additional parameters
    are given as parameters to the function call. Return
    identifier to cancel scheduling with after_cancel."""
    if not func:
    # I'd rather use time.sleep(ms*0.001)
    self.tk.call('after', ms)
    else:
    # XXX Disgusting hack to clean up after calling func
    tmp = []
    def callit(func=func, args=args, self=self, tmp=tmp):
    try:
    func(*args)
    finally:
    try:
    self.deletecommand(tmp[0])
    except TclError:
    pass
    name = self._register(callit)
    tmp.append(name)
    return self.tk.call('after', ms, name)

    def after_cancel(self, id):
    """Cancel scheduling of function identified with ID.

    Identifier returned by after or after_idle must be
    given as first parameter."""
    try:
    data = self.tk.call('after', 'info', id)
    # In Tk 8.3, splitlist returns: (script, type)
    # In Tk 8.4, splitlist may return (script, type) or (script,)
    script = self.tk.splitlist(data)[0]
    self.deletecommand(script)
    except TclError:
    # print "ekr: tcl error"
    pass
    # print "ekr: calling cancel",id
    self.tk.call('after', 'cancel', id)

    The "disgusting" tmp hack does not appear in after_cancel. I'm not sure
    what this does, especially since tmp.append(name) would appear to have no
    effect because tmp is not a global. My guess is that if the hack is needed
    in one place it might be needed in another.

    Summary: For now I can just call root.quit and rely on the killed flag to
    keep the after routine out of trouble, but it does appear that something
    isn't quite right. Does anyone have a clue what is going on? Thanks.

    Edward

    P.S. My "after" routine is initially created with after_idle, and
    thereafter reposts itself with after. Like this:

    # An internal routine used to dispatch the "idle" hook.
    def idleTimeHookHandler(*args):

    a = app()
    if a.killed: # New for Python 2.3...
    print "idleTimeHookHandler: killed"
    return

    # ...Do stuff

    # Requeue this routine after 100 msec.
    if a.idleTimeHook:
    a.afterHandler = a.root.after(a.idleTimeDelay,idleTimeHookHandler)
    else:
    a.afterHandler = None

    EKR
    --------------------------------------------------------------------
    Edward K. Ream email:
    Leo: Literate Editor with Outlines
    Leo: http://webpages.charter.net/edreamleo/front.html
    --------------------------------------------------------------------
     
    Edward K. Ream, Aug 29, 2003
    #1
    1. Advertising

  2. > Previous to Python 2.3 my app has destroyed the root Tk window using
    > root.destroy rather than the more usual root.quit. In Python 2.3 this does
    > not work so well...


    Oops. I forgot to mention that I am using Python 2.3.0 on Windows XP. It
    was installed using the standard Windows installer and so is running Tk
    8.4.3.

    Edward
    --------------------------------------------------------------------
    Edward K. Ream email:
    Leo: Literate Editor with Outlines
    Leo: http://webpages.charter.net/edreamleo/front.html
    --------------------------------------------------------------------
     
    Edward K. Ream, Aug 29, 2003
    #2
    1. Advertising

  3. "Edward K. Ream" <> schrieb im Newsbeitrag
    news:...
    > > Previous to Python 2.3 my app has destroyed the root Tk window using
    > > root.destroy rather than the more usual root.quit. In Python 2.3 this

    does
    > > not work so well...


    Your example is somewhat complicated to say the least. As I am not using
    2.3/8.4 at the moment I have not encountered like problems. But note that
    there are fundamental differences between Tk.quit and Tk.destroy. Tk.quit
    just leaves the event loop and touches no widget. See this example:

    from Tkinter import *
    m=Tk()
    Button(m,text="quit",command=m.quit).pack()
    mainloop()
    print "this is not The End"
    mainloop()

    Maybe destroy did not trigger Python garbage collection in 8.3 as expected,
    so data could have been erronously accessable. The memory leak would not
    have been to obvious because destroy is a rare instruction...

    May also be there is some bug with destroy in Tk 8,4 ;-)

    Kindly
    Michael P
    >
     
    Michael Peuser, Aug 29, 2003
    #3
  4. > Maybe destroy did not trigger Python garbage collection in 8.3 as
    expected,
    > so data could have been erroneously accessible.


    Thanks for your comments. Note that my app does nothing with Tk data after
    root.destroy(), so this shouldn't matter. The real mystery seems to be why
    the "after" routine is being called even after after_cancel.

    I suppose it is time to start digging into the Tk code...

    Edward

    P.S. My comments about the "disgusting" hack in the tkinter after method
    were dim. The tmp list is the default value of the tmp argument of the
    callit function, so whether tmp is global or not is irrelevant: changes to
    tmp will affect later calls to callit.

    EKR
    --------------------------------------------------------------------
    Edward K. Ream email:
    Leo: Literate Editor with Outlines
    Leo: http://webpages.charter.net/edreamleo/front.html
    --------------------------------------------------------------------
     
    Edward K. Ream, Aug 29, 2003
    #4
  5. I just took a look at TK's generic event code in tkEvent.c. It's really
    complex. For example:

    /*
    * There's a potential problem if a handler is deleted while it's
    * current (i.e. its procedure is executing), since Tk_HandleEvent
    * will need to read the handler's "nextPtr" field when the procedure
    * returns. To handle this problem, structures of the type below
    * indicate the next handler to be processed for any (recursively
    * nested) dispatches in progress. The nextHandler fields get
    * updated if the handlers pointed to are deleted. Tk_HandleEvent
    * also needs to know if the entire window gets deleted; the winPtr
    * field is set to zero if that particular window gets deleted.
    */


    This is precisely the situation in which my code fails.



    There may be a bug if the list is circular, in which case "updating" the
    event handler might not work because p.nextPtr might be p itself. Also, the
    winPtr hack might not work for time-specific events like "after" events.
    These are just guesses, and my guess is that there are bugs here somewhere.
    Lord! How does anyone ever get this kind of code to work?



    Anyway, I have a workaround...


    Edward
    --------------------------------------------------------------------
    Edward K. Ream email:
    Leo: Literate Editor with Outlines
    Leo: http://webpages.charter.net/edreamleo/front.html
    --------------------------------------------------------------------
     
    Edward K. Ream, Aug 29, 2003
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Dirk Meusel
    Replies:
    1
    Views:
    3,030
    Dirk Meusel
    Aug 19, 2003
  2. Andreas Klemt
    Replies:
    0
    Views:
    833
    Andreas Klemt
    Feb 1, 2004
  3. davidw
    Replies:
    3
    Views:
    426
    Curt_C [MVP]
    Aug 27, 2004
  4. Ton K.
    Replies:
    0
    Views:
    368
    Ton K.
    Jul 25, 2003
  5. W. eWatson

    after_cancel?

    W. eWatson, Apr 17, 2009, in forum: Python
    Replies:
    2
    Views:
    332
    W. eWatson
    Apr 18, 2009
Loading...

Share This Page