La función Callback to python de Tkinter Tcl se bloquea en Windows

Esta no es exactamente mi aplicación, pero es muy similar. He creado este código de prueba para mostrar el problema. Básicamente estoy tratando de llamar a tcl proc desde el hilo de python. Tcl proc devolverá la llamada a la función python cuando el resultado esté listo. Este resultado será publicado como un evento en el marco wx. Cuando corro como código puro de python, funciona bien. Cuando uso tcl proc, toda la aplicación falla sin ningún tipo de información. Si incremento el tiempo de espera (digamos 100), entonces funciona bien incluso con tcl. ¿Es la alta tasa de callback un problema o me estoy perdiendo algo más. Esta aplicación se ejecuta en Windows por cierto.

import wx from Tkinter import Tcl from threading import Thread import wx.lib.newevent from time import sleep CountUpdateEvent, EVT_CNT_UPDATE = wx.lib.newevent.NewEvent() tcl_code = 'proc tcl_worker {name max_count delay_time callback} { while {$max_count>0} {after $delay_time; $callback $name $max_count; incr max_count -1}}' # Option to use Tcl or not for counter # When enabled, Tcl will callback to python to upate counter value use_tcl = True # Option to create tcl interpreter per thread. # Test shows single interpreter for all threads will fail. use_per_thread_tcl = True count = 5000 wait_time = 1 ;# in milliseconds class Worker: def __init__(self,name,ui,tcl): global use_per_thread_tcl self.name = name self.ui = ui if use_per_thread_tcl: self.tcl = Tcl() self.tcl.eval(tcl_code) else: self.tcl = tcl self.target = ui.add_textbox(name) self.thread = Thread(target=self.run) self.thread.daemon = True self.thread.start() def callback(self, name, val): evt = CountUpdateEvent(name=self.name, val=val, target=self.target) wx.PostEvent(self.ui,evt) def run(self): global count, wait_time, use_tcl if use_tcl: # Register a python function to be called back from tcl tcl_cmd = self.tcl.register(self.callback) # Now call tcl proc self.tcl.call('tcl_worker', self.name, str(count), str(wait_time), tcl_cmd) else: # Convert milliseconds to seconds for sleep py_wait_time = wait_time / 1000 while count > 0: # Directly call the callback from here self.callback(self.name, str(count)) count -= 1 sleep(py_wait_time) class MainWindow(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, title="Decrement Counter", size=(600, 100)) self._DoLayout() self.Bind(EVT_CNT_UPDATE, self.on_count_update) def _DoLayout(self): self.sizer = wx.BoxSizer(wx.HORIZONTAL) self.panels = [] self.tbs = [] self.xpos = 0 def add_textbox(self,name): panel = wx.Panel(self, pos=(self.xpos, 0), size=(60,40)) self.panels.append(panel) tb = wx.StaticText(panel, label=name) tb.SetFont(wx.Font(16,wx.MODERN,wx.NORMAL,wx.NORMAL)) self.sizer.Add(panel, 1, wx.EXPAND, 7) self.tbs.append(tb) self.xpos = self.xpos + 70 return tb def on_count_update(self,ev): ev.target.SetLabel(ev.val) del ev if __name__ == '__main__': app = wx.App(False) frame = MainWindow(None) tcl = Tcl() tcl.eval(tcl_code) w1 = Worker('A', frame, tcl) w2 = Worker('B', frame, tcl) w3 = Worker('C', frame, tcl) w4 = Worker('D', frame, tcl) w5 = Worker('E', frame, tcl) w6 = Worker('F', frame, tcl) w7 = Worker('G', frame, tcl) w8 = Worker('H', frame, tcl) frame.Show() app.MainLoop() 

Cada objeto de intérprete de Tcl (es decir, el contexto que sabe cómo ejecutar un procedimiento de Tcl) solo se puede usar de forma segura desde el subproceso del sistema operativo que lo crea. Esto se debe a que Tcl no usa un locking de intérprete global como Python, y en su lugar hace un uso extensivo de datos específicos de subprocesos para reducir el número de lockings necesarios internamente. (El código Tcl bien escrito puede aprovecharse de esto para ampliar el hardware adecuado).

Debido a esto, debe asegurarse de que solo ejecute los comandos Tcl o las operaciones Tkinter desde un solo hilo; ese suele ser el hilo principal, pero no estoy seguro de si ese es el requisito real para la integración con Python. Puede iniciar subprocesos de trabajo si lo desea, pero no podrán utilizar Tcl o Tkinter (bueno, no sin precauciones muy especiales, que son más problemáticas de lo que vale la pena). En su lugar, deben enviar mensajes al hilo principal para que pueda manejar la interacción con la GUI; Hay muchas maneras diferentes de hacer eso.