after, after_cancel and Python 2.3

E

Edward K. Ream

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 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
 
E

Edward K. Ream

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
 
M

Michael Peuser

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
 
E

Edward K. Ream

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
 
E

Edward K. Ream

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top