für eine Gui benötige ich eine Möglichkeit, (wiederholte) Aufrufe zu
verzögern (falls sinnvoll), den eigentlichen Aufruf direkt zurückkehren
zu lassen und den eigentlichen Aufruf asynchron durchzuführen. Falls der
Ausgangsaufruf mehrfach eintrifft, reicht ein einziger,
zusammengefasster aus.
Das ist z.B. sinnvoll, wenn die Gui viele eintreffende Daten anzeigen
soll, man aber nicht bei jedem Eintreffen ein eigenes Update durchführen
kann.
Nach den (auch hier diskutierten) Versuchen, das ad-hoc in den passenden
Klassen zu implementieren, habe ich es abgespalten:
----------------------8<-------------------------------------------
import threading
class UpdateThread(threading.Thread):
def __init__(self, func):
threading.Thread.__init__(self)
self.setDaemon(True)
self.c = threading.Condition()
self.scheduled = False
self.func = func
def run(self):
while True:
self.c.acquire()
while not self.scheduled:
self.c.wait()
self.scheduled = False
self.c.release()
self.func()
def __call__(self):
self.c.acquire()
self.scheduled = True
self.c.notify()
self.c.release()
----------------------8<-------------------------------------------
Verwendet wird es etwa so:
def do_update():
expensive_gui_change()
...
update = UpdateThread(do_update)
Der Aufruf von update() kehrt nun unmittelbar zurück und wird das
do_update() starten. Wiederholte Aufrufe führen dazu, dass do_update()
erneut ausgeführt wird, aber nur so oft, dass es einmal nach dem letzten
update()-Aufruf ausgeführt wurde.
Zu der Implementierung habe ich jetzt zwei Fragen:
1. Das sieht recht allgemein verwendbar aus, sodass sowas vermutlich
irgendwo schon als Standardroutine existiert. Dann kann ich meinen Code
als Übung betrachten und wegwerfen. Ist das so?
2. Falls nicht: wie bekomme ich es hin, dass der Thread automatisch
beendet und weggeräumt wird, wenn er nicht mehr referenziert wird?
Viele Grüße
Ole
Ole Streicher schrieb:
> def run(self):
> while True:
> self.c.acquire()
> while not self.scheduled:
> self.c.wait()
> self.scheduled = False
> self.c.release()
> self.func()
Wait-Loops sollten immer so programmiert werden:
cond.acquire()
try:
while not some_condition:
cond.wait()
... # do stuff
finally:
cond.release()
Nützlich ist dabei, dass Conditions zugleich with-Context-Manager sind.
Deswegen kann man es auch so schreiben:
with cond:
while not some_condition:
cond.wait()
... # do stuff
Dabei wird acquire() intern vor dem Eintritt in den Block unterhalb des
with aufgerufen. Sollte dann im Block eine Exception fliegen, ist
garantiert, dass auch dann cond.release() aufgerufen wird. Bei deinem
Code gäbe es in diesem Fall leider eine Guru Meditation.
> def do_update():
> expensive_gui_change()
> ...
>
> update = UpdateThread(do_update)
>
> Der Aufruf von update() kehrt nun unmittelbar zurück und wird das
> do_update() starten. Wiederholte Aufrufe führen dazu, dass do_update()
> erneut ausgeführt wird, aber nur so oft, dass es einmal nach dem letzten
> update()-Aufruf ausgeführt wurde.
Dazu müsste der letzte Aufruf irgenwie wissen, dass er der letzte Aufruf
ist. Wenn du selber weisst, welcher der letzte ist, kannst du auch
einfach alle vorherigen weglassen und nur diesen verwenden und alles ist
gut. Wenn Du es aber nicht weisst, wie soll er es dann wissen?
> wie bekomme ich es hin, dass der Thread automatisch
> beendet und weggeräumt wird, wenn er nicht mehr referenziert wird?
IMO geht das nicht. Selbst wenn dein Code den Thread nicht mehr
referenziert, gibt es doch noch interne Referenzen der
Thread-Verwaltung, die erst weggehen, nachdem der Thread beendet wurde.
Wenn du willst, dass der Thread weggeht, sag es ihm. Dazu packst du eine
weitere Bedingung in den wait-Loop:
class DoAsync(object):
def __init__(self):
self._go_away = False
self._cond = Condition()
self._flag = False
def __call__(self):
while True:
with self._cond:
while not (self._flag or self._go_away):
if self._go_away:
return
self._cond.wait()
... # do stuff
def go_away(self):
with self._cond:
self._go_away = True
self._cond.notify()
do_stuff = DoAsync()
Thread(target=do_stuff).start()
...
# viel später:
do_stuff.go_away()
The Zen Of Python sagt: Explicit is better than implicit.
Gruß,
Mick.
Ole Streicher schrieb:
> def run(self):
> while True:
> self.c.acquire()
> while not self.scheduled:
> self.c.wait()
> self.scheduled = False
> self.c.release()
> self.func()
Wait-Loops sollten immer so programmiert werden:
cond.acquire()
try:
while not some_condition:
cond.wait()
... # do stuff
finally:
cond.release()
Nützlich ist dabei, dass Conditions zugleich with-Context-Manager sind.
Deswegen kann man es auch so schreiben:
with cond:
while not some_condition:
cond.wait()
... # do stuff
Dabei wird acquire() intern vor dem Eintritt in den Block unterhalb des
with aufgerufen. Sollte dann im Block eine Exception fliegen, ist
garantiert, dass auch dann cond.release() aufgerufen wird. Bei deinem
Code gäbe es in diesem Fall leider eine Guru Meditation.
> def do_update():
> expensive_gui_change()
> ...
>
> update = UpdateThread(do_update)
>
> Der Aufruf von update() kehrt nun unmittelbar zurück und wird das
> do_update() starten. Wiederholte Aufrufe führen dazu, dass do_update()
> erneut ausgeführt wird, aber nur so oft, dass es einmal nach dem letzten
> update()-Aufruf ausgeführt wurde.
Dazu müsste der letzte Aufruf irgenwie wissen, dass er der letzte Aufruf
ist. Wenn du selber weisst, welcher der letzte ist, kannst du auch
einfach alle vorherigen weglassen und nur diesen verwenden und alles ist
gut. Wenn Du es aber nicht weisst, wie soll er es dann wissen?
> wie bekomme ich es hin, dass der Thread automatisch
> beendet und weggeräumt wird, wenn er nicht mehr referenziert wird?
IMO geht das nicht. Selbst wenn dein Code den Thread nicht mehr
referenziert, gibt es doch noch interne Referenzen der
Thread-Verwaltung, die erst weggehen, nachdem der Thread beendet wurde.
Wenn du willst, dass der Thread weggeht, sag es ihm. Dazu packst du eine
weitere Bedingung in den wait-Loop:
class DoAsync(object):
def __init__(self):
self._go_away = False
self._cond = Condition()
self._flag = False
def __call__(self):
while True:
with self._cond:
while not (self._flag or self._go_away):
self._cond.wait()
if self._go_away:
return
Mick Krippendorf <mad....@gmx.de> writes:
> with cond:
> while not some_condition:
> cond.wait()
> ... # do stuff
Danke. Den Fall, dass die Funktion eine Exception wirft, hatte ich nicht
bedacht.
>> Der Aufruf von update() kehrt nun unmittelbar zurück und wird das
>> do_update() starten. Wiederholte Aufrufe führen dazu, dass do_update()
>> erneut ausgeführt wird, aber nur so oft, dass es einmal nach dem letzten
>> update()-Aufruf ausgeführt wurde.
> Dazu müsste der letzte Aufruf irgenwie wissen, dass er der letzte Aufruf
> ist. Wenn du selber weisst, welcher der letzte ist, kannst du auch
> einfach alle vorherigen weglassen und nur diesen verwenden und alles ist
> gut. Wenn Du es aber nicht weisst, wie soll er es dann wissen?
Er weiß es natürlich auch nicht. Die Logik ist hier folgende:
- ich habe eine Quelle von Datenänderungen, die sich auf die Anzeige
auswirken sollen (neue Daten, Userinput, ...)
- die Quelle sprudelt unregelmäßig
- ich möchte die Anzeige, soweit möglich, aktuell halten
Also soll mit jedem neuen Dateninput ein update der Anzeige getriggert
werden. Der Trigger soll asynchron in der Verarbeitung der Inputdaten
erfolgen, damit die Datenverarbeitung (und im Falle eines grafischen
Userinputs die GUI) nicht blockiert wird.
Die Verarbeitung selbst kann durchaus einige Sekunden dauern. Es ist
möglich, dass in dieser Zeit bereits neuer Input ankam, die Anzeige also
nicht mehr aktuell ist. In diesem Fall muss die Verarbeitung wiederholt
werden (die alte eventuell sogar abgebrochen). Nur wenn während der
Verarbeitung keine neuen Ereignisse eintrafen, ist die Anzeige danach
aktuell und auf dem Stand des letzten Ereignisses.
Stell Dir beispielsweise vor, man hat ein Datenfenster und eine Anzeige
von Fitergebnissen. Mit einem Mauszeiger kann ich in dem Datenfenster
den Startwert des Fits bestimmen. Wenn ich den verschiebe, möchte ich
möglichst rasch die Anzeige der Fitergebnisse haben. Das kann (auf
passenden Rechnern) so fix gehen, dass man keine Verzögerungen bemerkt,
es kann aber auch einige Sekunden dauern. Und natürlich brauche ich --
wenn ich den Mauszeiger schon weiterbewegt habe -- nicht mehr die
Ergebnisse der vorigen Startwerte.
Also wird das Bewege-Maus-Event mit dem Triggern der Berechnung
verknüpft. Man kann ihn nicht direkt mit der Berechnung verknüpfen, weil
dann die Oberfläche "hakelig" wird: die Anzeige der Mausposition (die ja
mit dem gleichen Event verbunden ist) würde nur noch nach einem Fit
erneuert.
Das scheint mir ein Standardproblem zu sein, deshalb hatte ich
eigentlich auch auf eine Standardlösung bei "Batteries included" Python
gehofft.
>> wie bekomme ich es hin, dass der Thread automatisch
>> beendet und weggeräumt wird, wenn er nicht mehr referenziert wird?
> IMO geht das nicht. Selbst wenn dein Code den Thread nicht mehr
> referenziert, gibt es doch noch interne Referenzen der
> Thread-Verwaltung, die erst weggehen, nachdem der Thread beendet
> wurde.
Gibt es Weak References in Python? Wenn ich die Funktion "func" dahin
verpacke, könnte man das testen:
def __init__(self, func)
...
self.func_ref = WeakReference(func)
def run(self):
while self.func_ref.exists():
...
In diesem Fall würde der Thread keine Referenz auf die übergebene
Funktion halten, die kann damit bei Bedarf weggeräumt werden, und man
müsste "nur noch" ein notify() beim wegräumen auslösen und im
UpdateThread behandeln.
> The Zen Of Python sagt: Explicit is better than implicit.
Python kommt mit eingebauter Müllabfuhr, die soll gefälligst auch
richtig aufräumen :-)
Im Ernst: Garbage Collection ist das beste Gegenbeispiel für Dein
Zen-Zitat.
Viele Grüße
Ole
Gibt es. Damit sieht die Klasse derzeit so aus:
------------------------------------8<----------------
import threading
import weakref
class DoAsync(threading.Thread):
def __init__(self, func):
threading.Thread.__init__(self)
self.setDaemon(True)
self._cond = threading.Condition()
self.scheduled = False
if func:
self._func = weakref.ref(func)
def run(self):
while self._func():
with self._cond:
while not self.scheduled and self._func():
self._cond.wait(60)
if not self._func():
break
self.scheduled = False
func = self._func()
if func:
func()
def __call__(self):
with self._cond:
self.scheduled = True
self._cond.notify()
------------------------------------8<----------------
Was mir daran nicht gefällt, ist die Tatsache, dass ich auf das
Verschwinden der Referenz pollen muss (mit wait(60)) -- damit bleibt der
Thread eine ganze Weile bestehen, auch wenn er nichts mehr
nutzt. Deutlich besser wäre es, wenn mich die weakref informiert, dass
die Referenz weggeräumt wurde. Aber wie erreiche ich das?
Viele Grüße
Ole
> Was mir daran nicht gefällt, ist die Tatsache, dass ich auf das
> Verschwinden der Referenz pollen muss (mit wait(60)) -- damit bleibt der
> Thread eine ganze Weile bestehen, auch wenn er nichts mehr
> nutzt. Deutlich besser wäre es, wenn mich die weakref informiert, dass
> die Referenz weggeräumt wurde. Aber wie erreiche ich das?
proxy(...)
proxy(object[, callback]) -- create a proxy object that weakly
references 'object'. 'callback', if given, is called with a
reference to the proxy when 'object' is about to be finalized.
Thomas
Das ginge glaub ich schöner mit einem threading.Event. Das kann man
setzen, löschen und aufs Gesetztwerden warten lassen.
class DoAsync(threading.Thread):
def __init__(self, func):
threading.Thread.__init__(self)
self.setDaemon(True)
self.scheduled = threading.Event()
if func:
self._func = weakref.ref(func)
# und wenn nicht? Dann passiert nix...
def run(self):
while self._func():
self.scheduled.wait(60)
if self.scheduled.is_set():
self.scheduled.clear()
func=self._func()
if not func: break
func()
def __call__(self):
self.scheduled.set()
> Was mir daran nicht gefällt, ist die Tatsache, dass ich auf das
> Verschwinden der Referenz pollen muss (mit wait(60)) -- damit bleibt der
> Thread eine ganze Weile bestehen, auch wenn er nichts mehr
> nutzt. Deutlich besser wäre es, wenn mich die weakref informiert, dass
> die Referenz weggeräumt wurde. Aber wie erreiche ich das?
Wie erwähnt mit proxy...
Thomas