folgendes Problem: ich habe eine GUI-Anwendung, die ihre Arbeit in einem
Thread erledigt.
Da ja bekanntlich GUI-Aufrufe nur aus dem eienn GUI-Thread zulässig
sind, will/muß ich anstehende GUI-Jobs (Aktualiserung etc.) entsprechend
queuen, so daß der eigentliche Aufruf innerhalb des GUI-Threads erledigt
wird.
Nun dachte ich eigentlich, das dafür geeignete Werkzeug sei
after_idle(). Allerdings scheint dem nicht so zu sein; wenn ich dieses
verwende, hagelt es "pythonw.exe hat ein Problem festgestellt und muss
beendet werden." (muß leider unter Windows arbeiten). Irgendwas in
tcl85.dll.
Offenbar ist auch der Aufruf von after_idle() aus einem Fremdthread
verboten, da es sich ja ebenfalls um eine Tk-Funktion handelt. Eine
Alternativlösung, wo die GUI alle x ms mittels after() immer wieder
dieselbe Funktion aufruft, die den nächsten GUI-Task aus einer Queue
zieht, funktioniert hingegen. (Aktivierung im untigen Programm durch
Setzen von POLLING auf 1).
Gibt es eine elegante Lösung, die ohne Polling auskommt? Sowas wie
after_idle(), nur eben in funktionierend? Sowas wie asyncExec() in Java?
Zusatzfrage: Auch wenn ich POLLING=1 habe, erscheint am Ende des Programms
>>> invalid command name "12598008callit"
while executing
"12598008callit"
("after" script)
WOher kommt das? Kann ich das vermeiden?
TIA,
Thomas
Es geht um folgendes Programm als Minimalbeispiel:
-----
#!/usr/bin/env python
# coding: latin1
from Tkinter import *
from threading import *
POLLING=0
import time
class CallLoop(object):
"""after_idle in funktionierend. period ist in ms.
"""
def __init__(self,master,period=5):
import Queue
self.period=period
self.queue=Queue.Queue()
self.master=master
master.after(0,self.poll)
def put(self,task,*a,**k):
"""Job in Queue legen."""
if a or k:
self.queue.put(lambda: task(*a,**k))
else:
self.queue.put(task)
def poll(self):
"""Endlosschleife."""
import Queue
try:
task=self.queue.get_nowait()
try:
task()
period=0
except: # swallow exceptions
pass
except Queue.Empty:
period=self.period
self.master.after(period,self.poll)
__call__=poll
class Application(Tk):
def __init__(self,use_call_loop):
Tk.__init__(self)
text1=StringVar()
text2=StringVar()
Entry(self,textvariable=text1).pack()
Entry(self,textvariable=text2).pack()
self.text=(text1,text2)
if use_call_loop:
cl=CallLoop(self)
self.my_after_idle=cl.put
else:
self.my_after_idle=self.after_idle
def settext(self,n,text):
self.my_after_idle(self.text[n].set,text)
def displayloop(self,n,it,t=.04):
for s in it:
self.settext(n,s)
time.sleep(t)
def mkcounter():
import itertools
return ("%06x"%i for i in itertools.count())
# Hauptprogramm:
app = Application(use_call_loop=POLLING)
Thread(target=app.displayloop,args=(0,mkcounter(),.03)).start()
Thread(target=app.displayloop,args=(1,mkcounter(),.05)).start()
app.mainloop()
-----
Nur vielleicht ein Tipp, der evtl. helfen k�nnte: Manchmal ist
kooperatives Multitasking ("Polling") angenehmer, weil deterministischer
und besser zu debuggen. In Python kann man hierzu geschickt
Generator-Funktionen (yield) nutzen. Dies hat den Vorteil, dass die
Funktion, die man unterbricht, um die Rechenzeit einer anderen zur
Verf�gung zu stellen, sich automatisch ihren eigenen Zustand merkt.
Siehe http://www.python.org/doc/2.3.5/ref/yield.html.
Dies ist meiner Erinnerung nach auch sehr sch�n in folgendem Buch
beschrieben:
Exploring Python von Markus Nix, Torsten Marek, Martin Grimme, und
Michael Weigend
Viele Gr��e
Thomas
> ja, es sollte definitiv eine Möglichkeit geben, aus einem Worker-Thread
> eine Nachricht an den Event-Dispatch-Thread abzusetzen, um ein Ereignis
> zu visualisieren. Ich kenne sie allerdings auch nicht.
Schade. Trotzdem danke für die Antwort. Meine bisherigen Google-Versuche
führten leider auch zu keinem Ergebnis, es kam immer nur der Rat mit dem
Pollen. Unschön, aber scheint wirklich die einzige Möglichkeit zu sein.
> Nur vielleicht ein Tipp, der evtl. helfen könnte: Manchmal ist
> kooperatives Multitasking ("Polling") angenehmer, weil deterministischer
> und besser zu debuggen. In Python kann man hierzu geschickt
> Generator-Funktionen (yield) nutzen.
Das stimmt - im allgemeinen Fall. Wenn aber - wie in meinem Fall - die
aufgerufene Funktion (oder Generator) einmal mehrere hundert ms oder gar
Sekunden brauchen kann, würde das dazu führen, daß während der Zeit die
GUI eben nicht ansprechbar ist. Weiteres Aufsplitten ist im vorliegenden
Fall leider nicht trivial möglich.
Viele Grüße,
Thomas