2016-08-04 44 views
1

Dies ist nicht genau meine Anwendung, aber sehr ähnlich. Ich habe diesen Testcode erstellt, um das Problem zu zeigen. Im Grunde versuche ich tcl proc aus Python-Thread aufzurufen. Tcl proc ruft die Python-Funktion zurück, wenn das Ergebnis fertig ist. Dieses Ergebnis wird als Ereignis zu wx frame gepostet. Wenn ich als reiner Python-Code laufe, funktioniert es gut. Wenn ich tcl proc verwende, stürzt die ganze App ohne irgendwelche Informationen ab. Wenn ich wait_time (sagen wir 100) erhöhe, dann funktioniert es auch mit tcl. Ist es die hohe Rate des Rückrufs ein Problem oder fehlt mir etwas anderes. Diese App läuft übrigens auf Windows.Callback zu Python-Funktion von Tkinter Tcl stürzt in Windows

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() 

Antwort

1

Jedes Objekt Tcl-Interpreter (das heißt, dass der Kontext weiß, wie eine Tcl-Prozedur ausgeführt werden) sicher aus dem OS-Thread wird nur kann, die es erstellt verwendet. Dies liegt daran, dass Tcl keine globale Interpreter-Sperre wie Python verwendet und stattdessen umfangreiche Thread-spezifische Daten verwendet, um die Anzahl der intern erforderlichen Sperren zu reduzieren. (Gut geschriebene Tcl-Code kann dies nutzen, sehr groß auf geeignete Hardware zu skalieren.)

Aus diesem Grund, Sie must sicher, dass Sie immer nur laufen Tcl-Befehle oder Tkinter Operationen von einem einzigen Faden; das ist normalerweise der Hauptthread, aber ich bin mir nicht sicher, ob das die wirkliche Voraussetzung für die Integration mit Python ist. Sie können Worker-Threads starten, wenn Sie möchten, aber sie können Tcl oder Tkinter nicht verwenden (gut, nicht ohne besondere Vorsichtsmaßnahmen, die mehr Probleme verursachen, als es wahrscheinlich wert ist). Stattdessen müssen sie Nachrichten an den Hauptthread senden, damit er die Interaktion mit der GUI verarbeiten kann. Es gibt viele verschiedene Möglichkeiten, dies zu tun.

+0

Vielen Dank für Ihre Antwort. Durch Setzen von 'use_per_thread_tcl' auf True in meinem Code erstellt jeder Thread seinen eigenen tcl-Interpreter. 'tcl_worker' proc wird von jedem Thread einzeln ausgewertet. Also ich denke, es gibt keine Interaktionen zwischen Tcl-Interpreten. –

+0

Sie erstellen die Tcl-Interpreter, bevor Sie Ihre run() -Methode eingeben, sodass sie alle im Hauptthread erstellt werden. Verschieben Sie die Erstellung möglicherweise in Ihre Ausführungsmethode. – schlenk

+0

Danke schlenk für den interessanten Vorschlag. Ich habe es nach Ihrem Vorschlag versucht. Der Tcl-Interpreter ruft nicht einmal den 'tcl_worker' proc auf. Die ganze App hängt, ohne etwas zu tun. Es freut mich, wenn ich die Anzahl der Arbeiter auf eins reduziere. Es scheint, als ob der tcl-Interpreter im Haupt- oder Unterthread erstellt werden kann, aber nur ein tcl-Interpreter ist erlaubt. Vielleicht verwendet Tkinter einige globale Variablen, die die Verwendung mehrerer Interpreter-Objekte einschränken. Als nächstes werde ich versuchen mit Multiprocessing-Modul, Erstellen von Tcl-Objekte in untergeordneten Prozessen. –