Tkinter, Sleep and threads

Mike Clarkson support at internetdiscovery.com
Tue Jun 26 02:13:13 EDT 2001


In _tkinter.c, in Tkapp_MainLoop():

 If Python is compiled WITH_THREADs, then it uses  a 20 msec. Sleep
loop to check signals and do Tcl events.

The current problem is threefold:
1) An idle Tkinter application consumes about 5% of the CPU, because
it is in a tight 20 msec Sleep loop. The equivalent Tk application
uses 0%.

2) Say you establish a ^C signal hanler, then create and use a Tkinter
dialog (that uses Tk's vwait, like ColorChooser). If you pop up the
dialog, enter the mainloop, and hit ^C, the signal handler will not be
called until you finish choosing a color. (I presume all other Python
threads will also be forced to wait.)

3) The 20 msec sleep is a hard sleep, so can effect mouse response
time, and is a real waste for asynccore users who could use it to poll
with a 20 msec timeout.

The 'right' way to do this, I think, is to put the
PyErr_CheckSignals() in a wrapper and schedule it periodically on the
Tcl event queue, and then use
		Tcl_DoOneEvent(0);
to use select in Tk to wait for events. 1) is fixed, 2) is fixed with
a max response lag determined by the scheduling, and 3) is cured, but
there may be side effects (see below).

It turns out that the code to make wrapper and schedule something
periodically on the Tcl event queue is already in _tkinter.c -
Tkapp_CreateTimerHandler. We could make a procedure (in Python or C),
that is inserted into the Tk event queue using
Tkapp_CreateTimerHandler. This function could check
for signals, and then insert itself again into the event queue in 1000
msec., or whatver granualarity you want for signal checking.

Before we do, we could make a small change to the current _tkinter.c
TimerHandler(). Let's add a PyErr_CheckSignals check at the beginning
- if a signal has been raised, we return NULL from the timer handler,
and set a flag so that the mainloop will exit, just as if we detected
the signal there.  I think this patch makes sense regardless of the
other points being discussed, and a patch against 2.1 is attached
below. 

(BTW, what is the difference in Python-C-API between Tkapp_MainLoop 
returning NULL and returning Py_None?  I'm not a C programmer).

Now if we do this, we can define our procedure to do nothing except
add itself again to the event queue 1000 msec. hence. The change
suggested above means that signals are checked even if we do nothing.
(Of course you might want to do other things besides nothing, but
let's leave that aside for the moment.)

With this procedure in place, you could change the Tkapp_MainLoop
while() to eliminate the Sleep, and use Tcl_DoOneEvent(0) - et voila
1) 2) 3) are solved.

But if we eliminate the Sleep, does that mean that a side effect is
no other threads except the Tcl thread gets to run?  Is it a side
effect  of the Sleep (within a Py_BEGIN_ALLOW_THREADS
region) that other threads are polled every loop iteration? (If so, I
assume Python callbacks from Tk would be effected.)

If so, can anyone think of a hybrid approach that uses TimerHandler to
allow other threads to run, as well as check for signals?

Many thanks,

Mike.

-------------------------------------------------------------------------------
*** _tkinter.c.dst	Thu Apr 19 14:30:22 2001
--- _tkinter.c	Fri Jun 22 20:27:42 2001
***************
*** 220,225 ****
--- 220,226 ----
  
  static PyObject *Tkinter_TclError;
  static int quitMainLoop = 0;
+ static int bailMainLoop = 0;
  static int errorInCmd = 0;
  static PyObject *excInCmd;
  static PyObject *valInCmd;
***************
*** 1615,1620 ****
--- 1616,1626 ----
  	PyObject *func = v->func;
  	PyObject *res;
  
+ 	/* mike: bail out if there is a raised signal or a quit
request */
+ 	if (quitMainLoop > 0 || PyErr_CheckSignals() != 0) {
+ 	  bailMainLoop = 1;
+ 	  return;
+ 	}
  	if (func == NULL)
  	  return;
  
***************
*** 1672,1677 ****
--- 1678,1684 ----
  		return NULL;
  
  	quitMainLoop = 0;
+ 	bailMainLoop = 0;
  	while (Tk_GetNumMainWindows() > threshold &&
  	       !quitMainLoop &&
  	       !errorInCmd)
***************
*** 1692,1698 ****
  		result = Tcl_DoOneEvent(0);
  #endif
  
! 		if (PyErr_CheckSignals() != 0)
  			return NULL;
  		if (result < 0)
  			break;
--- 1699,1705 ----
  		result = Tcl_DoOneEvent(0);
  #endif
  
! 		if (PyErr_CheckSignals() != 0 || bailMainLoop > 0)
  			return NULL;
  		if (result < 0)
  			break;




More information about the Python-list mailing list