Gui-Anderung einer Eigenschaft in einem anderen Modul

Fragen zu Tkinter.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch Ich bin mir auch nicht sicher, ob das für alle Python und tkinter Varianten gilt. Jedenfalls braucht BlackJack nicht meinen Code niedermachen. Mein Proxy enthält nämlich gar nichts von event_generate. Und wer diesem Befehl mißtraut, kann ja für Messages, die von außen kommen in seiner Applikation setzen:

Code: Alles auswählen

proxy.extern_trigger = lambda: root.after(1,proxy.trigger())
Und pollen muss man da auch nichts. Und für die internen Messages entweder event_generate oder so lassen, nämlich direkter Funktionsaufruf.

Oder eventuell noch besser, weil es da auch Probleme bei Guido van Rossum gab:

Code: Alles auswählen

proxy.extern_trigger = lambda: root.after(1,lambda: root.event_generate("<<SEND>>", when="tail"))
Und die internen dann bei direktem Funktionsaufruf belassen.

Kann ja jeder das nehmen, was ihm am sichersten erscheint.

Eines muss allerdings beim Proxy noch ergänzt werden, nämlich die Destroy Methode. Die ist nötig, wenn man wirklich mehre Tasks verwendet:

Code: Alles auswählen

def __del__(self): self.extern_proxy.undo_receiveAll(self)
Man muß dann nämlich die beim externen Proxy registrierten Callbacks löschen, wenn man seine Task beendet aber Python nicht. Ansonsten würde das System crashen, wenn noch jemand etwas sendet, aber der Empfänger nicht mehr existiert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist ja gerade das Problem, dass es verschiedene TkInter-Implementierungen von verschiedenen Python-Versionen auf unterschiedlichen Systemen gibt und man sich nur an das halten darf, was in der Dokumentation dazu steht. after muß vor oder innerhalb des Event-Loops aufgerufen werden, so dass sowohl Vorschlag 1 als auch Vorschlag 2 nicht funktioniert. Und statt __del__ gibt es auch saubere Lösungen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich habe den Eindruck, das mit den Implementierungsdetails ist Alfons völlig egal. Er nimmt es anscheinend in Kauf, dass ihm seine Bibliothek bei der nächsten Python-Version um die Ohren fliegen könnte, falls sich die Python-Entwickler entscheiden, etwas an den Implementierungsdetails zu verändern. Denn bekanntlich nimmt man bei nicht dokumentierten Details oftmals keine Rücksicht auf Abwärtskompatibilität.

Für mich ist diese Denkweise übrigens ein klarer Grund, von der ernsthaften Nutzung seiner Bibliothek abzuraten. Hier wird sich auf undokumentierte und sehr komplexe Details verlassen, ohne dass es irgendeine Art von Hinweis an den Benutzer in Bezug auf diese Risikofreudigkeit gibt. Hier geht es ja um weitaus mehr als ein paar Zeilen Python-Code, die man notfalls selbst schreiben könnte, falls sie mal aus der Standardbibliothek wegfallen oder "ungünstig" verändert werden.

Alfons hat Glück, dass es den Haftungsauschluss gibt, sodass ihn niemand dafür belangen kann, wenn ein Benutzer in gutem Glauben an sein Projekt irgendwann mal gehörig auf die Nase fällt... :)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Das ist ja gerade das Problem, dass es verschiedene TkInter-Implementierungen von verschiedenen Python-Versionen auf unterschiedlichen Systemen gibt und man sich nur an das halten darf, was in der Dokumentation dazu steht. after muß vor oder innerhalb des Event-Loops aufgerufen werden, so dass sowohl Vorschlag 1 als auch Vorschlag 2 nicht funktioniert. Und statt __del__ gibt es auch saubere Lösungen.
Vor oder innerhalb des Event-Loops hast Du immer. Und die Callbackfunktion wird dann im Thread der Event-Loop ausgeführt, allerdings nicht unbedingt während des Event-Loops und das ergibt das Problem beim GUI Update. Lösung zwei findet also im richtigen Thread statt, aber nicht unbedingt in der Event-Loop. Zu prüfen wäre, ob das dann ein Problem für event_generate ergibt, wenn man im GUI Thread ist, aber nicht in der Event-Loop. Der Callback für event_generate landet dann jedenfalls in der Event-Loop.

Eigentlich sollte man erwarten, dass die Programmierer event_generate besser programmieren oder aber einen ähnlichen Befehl machen, für nur solche virtuellen events. Denn in event_generate ist ja auch hineingepackt, dass man etwa Mausklicks und ähnliches simuliert. Ein einfacherer Befehl für nur solche virtuellen events wäre sicher leicht threadsicher zu implementieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@snafu Schau Dir meine Proxy an. Ein event_generate ist da nicht drin und somit auch nicht in meiner Bibliothek. Da kann der Programmierer frei einstellen, wie er dann die beiden Trigger proxy.trigger und proxy.extern_trigger setzen möchte. Wenn er nur den Proxy für messages in der GUI Task braucht, dann braucht er gar nichts zu setzen, denn proxy.trigger steht auf direktem Funktionsaufruf und proxy.extern_trigger auf noop(). Also dass externe Messags nur in die Queue gelegt werden ohne trigger. Kann man ja auch mit pollen über proxy.trigger() abholen oder durch senden einer dummy Message, etwa proxy.send('DUMMY'). Wenn man seine GUI Callbacks über proxy.send('execute_function',mycallback) realisisiert, werden die externen Messages spätestens beim nächsten GUI event mit abgeholt. Da könnte man genauso gut ein <B1-Motion> event setzen, denn die Maus wird man schon irgendwann einmal bewegen, oder? Oder hat jemand noch eine Idee ohne pollen. Etwa sonstige GUI events, die sich bestimmt oft ereignen?

Normalerweise wäre zu erwarten, dass jemand der mit der Maus klickt, daraufhin mit ihr noch wackelt. Nur wenn es sich um Anzeigen von einem externen Gerät handelt und man nur gelegentlich auf den Bildschirm schaut, dann bräuchte man dafür den externen Trigger.

after funktioniert unter Windows, hat aber auf dem Mac Probleme.

Also wäre schön, wenn man Lösung zwei überprüfen könnte, ob die problematisch ist.

Und wie gesagt, das hat alles nichts mit dem Code für meinen Proxy zu tun. Man kann von seiner Applikation aus die Trigger umstellen.

Wenn wir keine Universallösung finden, dann muss man eben je nach Umgebung es so machen, dass es entsprechend funktioniert, also für Windows so und Mac eben so. after weckt eben nicht bei jedem System die mainloop auf. Und event_generate funktioniert wohl auch nicht überall. Und Mausbewegung hat man auch nicht in jedem Fall.

Also würde after mit triggern von event_generate überall funktionieren?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

snafu hat geschrieben:Für mich ist diese Denkweise übrigens ein klarer Grund, von der ernsthaften Nutzung seiner Bibliothek abzuraten. Hier wird sich auf undokumentierte und sehr komplexe Details verlassen, ohne dass es irgendeine Art von Hinweis an den Benutzer in Bezug auf diese Risikofreudigkeit gibt.
Snafu behaupte nicht irgendetwas. Dann zeige mir hier irgendeine Zeile mit undokumentierten komplexen Details. Ich habe jetzt _del__ eingefügt und nach event.wait() muss event.clear() kommen, sonst geht die loop nachher nicht schlafen und das geht auf die Laufzeit. Hier ist eine Queue drin und und von threading der Event und das ist alles dokumentiert:

Code: Alles auswählen

import queue
import threading 
 
class Proxy:
 
    def __init__(self,extern_proxy=None):
        if extern_proxy == None: self.extern_proxy = self
        else: self.extern_proxy = extern_proxy
        self.reset()

    def __del__(self): self.extern_proxy.undo_receiveAll(self)

    def noop(self): pass

    def reset(self):
        self.Dictionary = {}
        self.owners = {}
        self.Queue = queue.Queue()
        self.Queue_HighPrio = queue.Queue()
        self._register("execute_function",lambda msg: msg())
        self.running = False
        self.trigger = self.do_work
        self.extern_trigger = self.noop

    def work(self,*args):
        if not self.Queue_HighPrio.empty(): data = self.Queue_HighPrio.get()
        elif not self.Queue.empty(): data = self.Queue.get()
        else: return False

        msgid = data[0]
        msgdata = data[1]
        if msgid in self.Dictionary:
            receivers = self.Dictionary[msgid].items()
            for receive,packed in receivers:
                if packed: receive((msgid,msgdata))
                else: receive(msgdata)
        return True

    def do_work(self,*args):
        if self.running: return
        self.running = True
        while self.work(): pass
        self.running = False

    def set_trigger(self,trigger):
        self.trigger = trigger
        self.extern_trigger = trigger

    def loop(self):
        self.event = threading.Event()
        self.set_trigger(self.event.set)
        self.event.set()
        while True:
            self.event.wait()
            self.event.clear()
            while self.work(): pass

    # sending ==========================================================

    def send(self,msgid,msgdata=None):
        self.Queue.put((msgid,msgdata))
        self.trigger()

    # extern send and receive callbacks ==========================================

    def do_send_extern(self,message_ids):
        for mid in message_ids: self.do_receive(self,mid,self.send_extern,True)

    def send_extern(self,message): self.extern_proxy.send(message[0],message[1])

    def do_receive_extern(self,message_ids):
        for mid in message_ids: self.extern_proxy.do_receive(self,mid,self.receive_extern,True)

    def receive_extern(self,message):
        self.Queue.put(message)
        self.extern_trigger()

    # register receiver ================================================

    def do_receive(self,owner,msgid,receive,packed=False):
        self.Queue_HighPrio.put(("execute_function",lambda: self._do_receive(owner,msgid,receive,packed)))
        self.trigger()

    def _do_receive(self,owner,msgid,receive,packed):
        if not owner in self.owners: self.owners[owner] = {}
        self.owners[owner][receive]=msgid
        self._register(msgid,receive,packed)

    def _register(self,msgid,receive,packed=False):
        if msgid not in self.Dictionary: self.Dictionary[msgid] = {}
        self.Dictionary[msgid][receive] = packed

    # unregister receiver ================================================

    def undo_receive(self,owner,msgid,receive):
        self.Queue_HighPrio.put(("execute_function",lambda: self._undo_receive(owner,msgid,receive)))
        self.trigger()
 
    def _undo_receive(self,owner,msgid,receive):
        if owner in self.owners:
            if receive in self.owners[owner]: del self.owners[owner][receive]
        self._unregister1(msgid,receive)

    def _unregister1(self,msgid,receive):
        if msgid in self.Dictionary:
            receivers = self.Dictionary[msgid]
            if receive in receivers:
                del receivers[receive]
                if len(receivers) == 0: del self.Dictionary[msgid]

    # unregister Owner ================================================

    def undo_receiveAll(self,owner):
        self.Queue_HighPrio.put(("execute_function",lambda: self._undo_receiveAll(owner)))
        self.trigger()

    def _undo_receiveAll(self,owner):
        if owner in self.owners:
            messages = self.owners[owner]
            del self.owners[owner]
            for receive,msgid in messages.items(): self._unregister1(msgid,receive)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons Mittelmeyer:
Das after unter OSX nicht funktioniert, würde ich als Bug von Tcl/Tk ansehen. Heisst - müssen die Maintainer reparieren (ist vllt. schon repariert?). Als Endbibliotheksprogrammierer würde ich dafür maximal einen interims-Fix vorhalten, um die eigene Funktionalität sicherstellen zu können.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch Dann setzt man in seiner Applikation einfach:

Code: Alles auswählen

proxy.set_trigger(lambda: root.after(0,proxy.work))
Habe es ausprobiert und funktioniert. Und wer noch mehr Sicherheit braucht, kann ja auch proxy.do_work nehmen. proxy.work bearbeitet nur einen Queueeintrag und proxy.do_work alles was in der Queue ist. Falls irgendwie after einmal versagen würde, dann wird das eben beim nächsten Mal mitverarbeitet.

Und wer ganz ganz ganz viel Sicherheit will, dass after auch wirklich keine Aussetzer hat, kann ja noch dazu eine after trigger Schleife machen. Wie gesagt, für interne Messages und keine weiteren Threads zum Messageaustausch oder nur senden an andere Threads und nicht empfangen ist das alles nicht nötig.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons Mittelmeyer:
Eww, da hast Du schon wieder ein nicht abgeschlossenes Lambda. Änder doch mal nach der Definition von dem proxy.set_trigger.... `root` auf was anderes und trigger dann mal das Lambda. Et voila!
Das musst Du an solchen Stellen sehr sauber programmieren, sonst fliegen Dir diese Lambdas um die Ohren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch Das ist ja nicht mein Code, sondern nur was man dort angeben könnte. Dann gebt es eben richtig an. Und anzunehmen wäre ja, dass man in seiner GUI Applikation root nicht ändert, oder? OK dann eben:

Code: Alles auswählen

proxy.set_trigger(lambda func = root.after, w = proxy.work: func(0,w))
Zuletzt geändert von Alfons Mittelmeyer am Freitag 4. September 2015, 13:31, insgesamt 1-mal geändert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Alfons Mittelmeyer hat geschrieben:@jerch Das ist ja nicht mein Code, sondern nur was man dort angeben könnte. Dann gebt es eben richtig an. Und anzunehmen wäre ja, dass man in seiner GUI Applikation root nicht ändert, oder?
Das ist unsauber, und warum soll ich nicht frei Bezeichner vergeben können? Richtig wäre - eine sauber spezifizierte API bauen, z.B. festlegen, dass callbacks in set_trigger root per Parameter erwarten müssen usw.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@jerch: after funktioniert doch wie es soll, nämlich wenn es im gleichen Thread wie der Mainloop aufgerufen wird. Da Tkinter dokumentiert nicht threadsafe ist, ist das kein Bug.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Ich verlor leider den Faden betreff deines Vorhabens. Ich erstellte folgendes Skript, welches auf der Basis mit after und Queue-Polling aufbaut. Nun meine Frage an dich wie würdes du jetzt dein Art von Event-Handling (proxi usw.) mit diesem Skript kombinieren damit die Polling-Methode mit after wegfällt. Hier mein Skript:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import threading
from time import sleep

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
    import Queue as qu
except:
    # Tkinter for Python 3.xx
    import tkinter as tk
    import queue as qu

APP_TITLE = "Thread-Flasher"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 200
APP_HEIGHT = 200

FONT_COUNT_LABEL = ('Helvetica', 30, 'bold')
COLOR_FLASHER_OFF = 'brown4'
COLOR_FLASHER_ON = 'red'
LOOP_TIME = 0.2     # Seconds
POLLING_TIME = 10   # Milliseconds
QUEUE_SIZE = 10
MAX_COUNT = 20


class MyThread(threading.Thread):

    def __init__(self, queue=None):
        threading.Thread.__init__(self)
        self.queue = queue

        self.count = 0
        self.running = True
        self.start()

    def stop(self):        
        self.running = False

    def run(self):
        while self.running:
            self.count += 1
            self.update_queue()
            sleep(LOOP_TIME)

    def update_queue(self):
        self.queue.put(self.count)
        self.queue.join()

    def enable_thread(self, state=False):
        """Abschalten des Thread-Timers"""
        self.running = state
        
class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.close)
        tk.Frame.__init__(self, master)
        
        self.toggle = False
        
        self.build_gui()

        self.queue = qu.Queue(QUEUE_SIZE)
        self.my_thread = MyThread(queue=self.queue)
        self.queue_polling()
        
    def build_gui(self):
        self.var_count = tk.IntVar()
        tk.Label(self, textvariable=self.var_count, font=FONT_COUNT_LABEL
            ).pack(expand=True)
        self.var_count.set(0)
        
        self.indicator = tk.Canvas(self, bg=COLOR_FLASHER_OFF, relief='raised',
            bd=4, highlightthickness=0, width=100, height=100)
        self.indicator.pack(expand=True, padx=2, pady=2)

    def queue_polling(self):
        if self.queue.qsize():
            #~~ In der Queue sind Daten vorhanden!
            try:
                #~~ Lese die Daten aus der Queue
                data = self.queue.get()
                self.update_flasher(data)
                self.queue.task_done()
            except qu.Empty:
                pass

        self.after(POLLING_TIME, self.queue_polling)
    
    def toggle_indicator(self):
        self.flash_indicator(self.toggle)
        self.toggle = not self.toggle

    def flash_indicator(self, state):
        if state:
            self.indicator.configure(bg=COLOR_FLASHER_ON)
        else:
            self.indicator.configure(bg=COLOR_FLASHER_OFF)

    def update_flasher(self, data):
        self.toggle_indicator()
        self.var_count.set(data)
        if data == MAX_COUNT:
            self.flash_indicator(False)
            self.my_thread.stop()
                                       
    def close(self):
        print("Shutdown-Application")
        self.master.destroy()
    
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
   
    app = Application(app_win).pack(fill='both', expand=True, padx=6, pady=6)
    
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()
Danke im voraus für deine Bemühung.

Gruss wuf :wink:
Take it easy Mates!
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sirius3:
Für mich las sich die Beschreibung in der Mail wie ein Abstimmungsproblem zwischen Cocoa- und Tcl-Eventloop. Daher der Bugverdacht. Da die Mail eine Weile alt ist, gehe ich davon aus, dass das bereits behoben wurde. Ob und wie das eine Auswirkung auf single threaded hat(te) - kA, kann es selbst nicht testen.

@Alfons Mittelmeyer:
Ok, wenn das der "Usercode" ist, ist das lambda ok. Wenn man dem User Pythonpower geben möchte, gehört auch das scoping dazu. Ich würde trotzdem die essentiellen Schnittstellen als eine Art Interface sauber festklopfen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@jerch: after funktioniert doch wie es soll, nämlich wenn es im gleichen Thread wie der Mainloop aufgerufen wird. Da Tkinter dokumentiert nicht threadsafe ist, ist das kein Bug.
Ich habe gesehen, dass anscheinend auch solche Bugs existieren:
Is Tkinter's thread safety new? Because after I started getting crashes, I did my due diligence in Google and found a number of people writing about how it was necessary to use a Queue and Tkinter's after() timer, to draw from multiple threads. And in experiments in 2.7.1 on Windows, it turned out that it wasn't just the drawing calls from a thread other than the mainloop() thread, that invited a crash; even just trying to set an .after() timer from a different thread caused tracebacks, randomly.
Wenn es natürlich so ist und event_generate nicht für alle tkinter Versionen geht, bleibt wohl wirklich nichts anderes mehr als die Pollschleife mit after und after_idle für messages von aussen (extern_trigger also auf noop lassen). Für die anderen (trigger) darf man dann entweder event_generate oder after nehmen oder bleibt bei direktem Funktionsaufruf.

Warum ist das nur so ein Schrott? Das hätte ich nicht gedacht!
Eigentlich sollte man, um Quatitäts tkinter Versionen zu bekommen, sich nicht nach solchen Bugs richten, sondern darauf bestehen, dass die berichtigt werden und man ansonsten diesen Schrott nicht nimmt.

Und das Problem ist da ja in bestimmten Fällen auch, dass man etwa bei batteriebetriebenen Geräten nicht ständig die GUI aufwecken will durch Pollen, sondern nur bei Bedarf.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Noch eine andere Lösung habe ich gesehen, nämlich mit os.pipe(): http://stackoverflow.com/questions/7141 ... m-in-queue
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Alfons Mittelmeyer hat geschrieben:Warum ist das nur so ein Schrott? Das hätte ich nicht gedacht!
Eigentlich sollte man, um Quatitäts tkinter Versionen zu bekommen, sich nicht nach solchen Bugs richten, sondern darauf bestehen, dass die berichtigt werden und man ansonsten diesen Schrott nicht nimmt.
Für mich war Tkinter nie ein ernstzunehmendes GUI-Toolkit für Python. Mir fehlt da allein schon die Widgetvielfalt und andere Möglichkeiten, welche Qt und Gtk mitbringen. Dafür gibts auch schon fertige Designerwerkzeuge etc.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch Dann widme ich mich wieder meinem GUI Designer. Andere Tasks brauch ich da nicht. Und sorry, dass ich von Vorstellungen ausging, dass so etwas wie tkinter professionelle Software wäre und ich da wohl mit einigen zusammenstieß. Konnte mir eben nicht vorstellen, dass es da so eine Menge an Bugs gibt und dass man deswegen so unnötig erscheinendes Polling machen muss.

Wenn mir noch eine Lösung einfallen sollte, auf überall funktionierende Art und schnell mit anderen Tasks zu kommunieren, dann werd ich das dann bei Gelegenheit erwähnen. Aber solche Lösungen haben andere wohl auch schon gesucht und nichts gefunden, das überall sicher funktioniert. Die eine mit der Pipe könnte ich ja mal probieren.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Alfons Mittelmeyer hat geschrieben:

Code: Alles auswählen

import queue
import threading 
 
class Proxy:
    ...
Sorry, aber ich verstehe nicht, was dieser Code tut. Kannst du es mir mal in groben Zügen auf Deutsch erklären?
In specifications, Murphy's Law supersedes Ohm's.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch habe es jetzt ohne pollen geschafft, nämlich mit einer pipe:

Code: Alles auswählen

import os
pipe_read, pipe_write = os.pipe() ;
pipe_write = os.fdopen(pipe_write, 'w')

def pipe_flush():
	pipe_write.write('x')
	pipe_write.flush()


def pipe_trigger(*args):
    os.read(pipe_read,1)
    proxy.trigger()

proxy.trigger=lambda func = root.after, w = proxy.work: func(0,w)
proxy.extern_trigger = pipe_flush
root.tk.createfilehandler(pipe_read, tk.READABLE, pipe_trigger)
Weiss nur nicht, ob ich das gut geschrieben habe und ob es unter Windows funktioniert. Also auf pipe_flush in einem andern Thread sollte dann (hoffentlich im GUI Thread) pipe_trigger aufgerufen werden. Bei mir hatte es funktioniert.
Antworten