Threading: asynchronisierer

01/10/2009 - 16:28 von Ole Streicher | Report spam
Hallo,

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
 

Lesen sie die antworten

#1 Mick Krippendorf
02/10/2009 - 01:23 | Warnen spam
Hallo.

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.

Ähnliche fragen