Seite 1 von 1

yscrollcommand=scollbar.set crash...

Verfasst: Mittwoch 23. Juli 2014, 17:30
von jens
Hier mal ein Test code, herrausgepopelt aus DragonPy:

Code: Alles auswählen

import Tkinter
import threading

class TkTest(object):
    def __init__(self):
        self.root = Tkinter.Tk()
        self.root.title("Test")
        self.root.geometry("+500+300")
        self.text = Tkinter.Text(
            self.root,
            height=20, width=80,
            state=Tkinter.DISABLED
        )
        scollbar = Tkinter.Scrollbar(self.root)
        scollbar.config(command=self.text.yview)

        self.text.config(
            yscrollcommand=scollbar.set, # XXX
        )

        scollbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
        self.text.pack(side=Tkinter.LEFT, fill=Tkinter.Y)

        self.root.bind("<Destroy>", self.destroy)

        self.root.update()
    def destroy(self, event=None):
        self.root.quit()

    def new_output_char(self, char):
        self.text.config(state=Tkinter.NORMAL)
        self.text.insert("end", char)
        self.text.see("end")
        self.text.config(state=Tkinter.DISABLED)

    def mainloop(self):
        self.root.mainloop()


def fill_with_chars(tk_win):
    tk_win.new_output_char("X")
    t = threading.Timer(0.001, fill_with_chars, args=[tk_win])
    t.start()

if __name__ == "__main__":
    tk_win = TkTest()
    fill_with_chars(tk_win)
    tk_win.mainloop()
Wegen dem yscrollcommand=scollbar.set in Zeile 18, crashed Tk recht schnell... Zumindest läuft es besser, wenn es auskommentiert ist.
Aber auch, wenn es auskommentiert ist, gibt es einen Crash, wenn man den Scrollbalken nutzten will.

Tracebacks sind unterschiedlich. Meist innerhalb von new_output_char()

Ich vermute mal, das ich irgendwie ein thread locking vornehmen muß. Aber wie?

Re: yscrollcommand=scollbar.set crash...

Verfasst: Mittwoch 23. Juli 2014, 17:52
von EyDu
Du kannst nicht einfach so aus einem Thread auf die GUI zugreifen. Die meisten Toolkits sind in diese Richtung nicht abgesichert und schmieren deshalb früher oder später ab. Im Falle von Tkinter solltest du die after-Methode verwenden. Mit dieser kannst du dann beispielsweise eine (threadsichere!) Queue abarbeiten, welche von einem Thread befüllt wurde.

Re: yscrollcommand=scollbar.set crash...

Verfasst: Mittwoch 23. Juli 2014, 19:09
von jens
Danke für den Tipp. Ich hab es mal so gemacht:

Code: Alles auswählen

#!/usr/bin/python3 -O
# coding: UTF-8


import Tkinter
import threading
import Queue


class TkTest(object):
    def __init__(self, input_queue):
        self.input_queue = input_queue

        self.root = Tkinter.Tk()
        self.root.title("Test")
        self.root.geometry("+500+300")
        self.text = Tkinter.Text(
            self.root,
            height=20, width=80,
            state=Tkinter.DISABLED
        )
        scollbar = Tkinter.Scrollbar(self.root)
        scollbar.config(command=self.text.yview)

        self.text.config(
            yscrollcommand=scollbar.set, # XXX
        )

        scollbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y)
        self.text.pack(side=Tkinter.LEFT, fill=Tkinter.Y)
        self.root.bind("<Destroy>", self.destroy)
        self.root.update()

    def destroy(self, event=None):
        self.root.quit()

    def new_output_char(self, char):
        self.text.config(state=Tkinter.NORMAL)
        self.text.insert("end", char)
        self.text.see("end")
        self.text.config(state=Tkinter.DISABLED)

    def add_input_interval(self):
        while not input_queue.empty():
            char = input_queue.get(1)
            self.new_output_char(char)
        self.root.after(100, self.add_input_interval)

    def mainloop(self):
        self.add_input_interval()
        self.root.mainloop()


def fill_with_chars(input_queue):
    input_queue.put("X")
    t = threading.Timer(0.0001, fill_with_chars, args=[input_queue])
    t.start()

if __name__ == "__main__":
    input_queue = Queue.Queue(maxsize=1)

    tk_win = TkTest(input_queue)

    fill_with_chars(input_queue)
    tk_win.mainloop()
So richtig testen kann ich es gerade nicht, denn auch die erste Version läuft unter Linux ohne Absturz...

Re: yscrollcommand=scollbar.set crash...

Verfasst: Mittwoch 23. Juli 2014, 21:21
von EyDu
Das prinzipielle Vorgehen ist so richtig, allerdings ist deine add_input_interval-Methode noch ein wenig anfällig. Bei Queues ist es eine schlechte Idee zu prüfen ob sich noch Elemente in der Warteschlange befinden und dann anschließend das Element mittels get zu holen. Dabei kann es nämlich passieren, dass empty False zurückliefert, ein anderer Thread etwas aus der Warteschlange nimmt und du dann mit get versuchst ein Element zu holen, obwohl keins mehr drin ist. Das blockiert dann deine GUI, was nicht passieren darf. In deinem Fall kann das nicht passieren, aber so etwas ändert sich gerne schnell. Daher solltest du immer darauf achten.

Ich bin mir auch nicht ganz sicher, ob du den get-Aufruf so verstanden hast, wie er funktioniert. get erwartet als ersten Parameter die Angabe, ob der Aufruf blockieren soll oder eben nicht. Das aktivierst du und, wie ich oben schon schrieb, könntest damit auch die GUI blockieren. Hier mal eine Variante, bei der die Probleme nicht auftreten können:

Code: Alles auswählen

def add_input_inverval(self):
    try:
        while True:
            char = input_queue.get(False)
            self.new_output_char(char)
    except Queue.Empty:
        pass

    self.root.after(100, self.add_input_interval)
Dann stellt sich noch die Frage, ob du wirklich die maximale Größe der Queue setzen willst. Du willst mittels Timer alle 0.0001 Sekunden ein neues Element in die Queue einfügen, frags die Warteschlange aber nur alle 0.1 Sekunden ab. Da der put-Aufruf blockierend ist, wird der Timer auch nur alle 0.1 Sekunden aufgerufen. Wahrscheinlich nicht ganz das gewünschte Verhalten.

Re: yscrollcommand=scollbar.set crash...

Verfasst: Mittwoch 23. Juli 2014, 21:39
von jens
Danke! Muß ich noch umsetzten ;)

Aktuell ist noch die schlechtere Lösung drin: https://github.com/jedie/DragonPy/commi ... 4914da9dca

Re: yscrollcommand=scollbar.set crash...

Verfasst: Donnerstag 24. Juli 2014, 13:42
von jens
So, nun Aufgeräumt: https://github.com/jedie/DragonPy/commi ... 820f40de8c

Weiß gar nicht mehr wo dieses .get(1) her kam :shock: