Threads - Zwischenergebnisse an GUI übergeben

Plattformunabhängige GUIs mit wxWidgets.
Antworten
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Moin!
Ich habe eine Funktion in einer Klasse, die Berechnungen durchführt und am Ende das Ergebnis über return an eine GUI Klasse zurückgibt.

Als Erweiterung würde ich gerne schon Zwischenergebnisse ausgeben und vielleicht auch den Fortschritt der Berechnung anzeigen.

Dazu lasse ich die Berechnung in einem eigenen Thread laufen.

Muss ich mit wx.CallAfter() arbeiten, um mir schon während der Laufzeit Zwischenergebnisse liefern zu lassen?

Danke
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Könnte mir vielleicht jemand erklären, wie ich wx.CallAfter() kann?

Bisher rufe ich eine Funktion ganz normal auf und lasse mir die Ergebnisse zurückgeben:

Code: Alles auswählen

ergebnis = test.run()
Wie würde ich sowas mit einem Thread umsetzen?
ProgChild
User
Beiträge: 210
Registriert: Samstag 9. April 2005, 10:58
Kontaktdaten:

tomate hat geschrieben:Dazu lasse ich die Berechnung in einem eigenen Thread laufen.
Eine bessere lösung wäre es, einfach die GUI upzudaten, wärend deiner Berechnung. Dann musst du dich nicht mit Thread-Synchronisation herumschlagen.

http://www.wxwidgets.org/manuals/2.6/wx ... ppdispatch
Es ist nett, freundlich zu sein.
Auch nett: [url=http://www.progchild.de]Homepage[/url]
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

tomate hat geschrieben:Muss ich mit wx.CallAfter() arbeiten, um mir schon während der Laufzeit Zwischenergebnisse liefern zu lassen?
Hallo tomate!

Ja! Aber dazu brauchst du etwas, was du von deiner arbeitenden Funktion aus an die GUI zurück geben kannst.

Suche hier im Forum nach "wx.CallAfter" und gehe die Suchergebnisse von unten nach oben durch. Nimm dir die Zeit. Dort findest du Anleitungen, Hintergründe und Beispiele.

Wenn du in deiner arbeitenden Funktion eine Schleife durchläufst, so dass du immer wieder zwischendurch die GUI am Laufen halten kannst (mit wx.YieldIfNeeded), dann musst du nicht unbedingt auf einen zusätzlichen Thread ausweichen. Aber ein Thread ist nicht schlecht, dann bleibt die GUI auf jeden Fall flüssig und bedienbar.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Danke für Eure Hilfe.
Ich denke, ich werde es mit Threads versuchen, damit die GUI bedienbar bleibt.

Normalerweise sollte man ja möglichst GUI und Logik trennen oder?
Ich habe im Moment mein Programm auf zwei Dateien aufgeteilt. Eine für die Logik, eine für die GUI.
Sehe ich es richtig, dass ich wenn ich mit wx.CallAfter arbeite, automatisch auch GUI-Elemente in meiner Logik-Datei einbauen muss?

Mein Problem ist, dass ich in meiner GUI-Datei Klassen aus der Logik-Datei importiere. Aber wenn ich Threads nutzen will, müsste ich aus der Logik-Datei eigentlich auch Zugriff auf die GUI haben oder? Wie mache ich das?

Um mal auf ein Beispielvon dir zurückzukommen:

Code: Alles auswählen

class MyWorker(threading.Thread):
   
    def __init__(self, finished_function = None):
        threading.Thread.__init__(self)
        self.canceled = threading.Event()
        self.finished_function = finished_function
   
   
    def run(self):
        for i in xrange(10):
            if self.canceled.isSet():
                print "Gestoppt..."
                break
            print "Ich arbeite..."
            time.sleep(1)
       
        if self.finished_function:
            self.finished_function()
   
   
    def stop(self):
        self.canceled.set()
Könnte ich auch in jedem Schleifendurchlauf etwas an die GUI zurückgeben? Also z.b. "SchleifeX"
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo tomate!

Ich habe jetzt keinen Bock auf erklären:

example_lib.py:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import time


def my_worker(start = 0, stop = 10, step = 1, status_func = None, finished_func = None):
    """
    Arbeitende Funktion.
    
    :param start: Gibt die Zahl an, ab der gezählt werden soll.
    :param stop: Gibt die Zahl an, bis zu der gezählt werden soll.
    :param step: Schrittwert
    :param status_func: Wenn angegeben, dann wird eine Funktion erwartet, an die
        man als Parameter den Zwischenstand übergeben kann.
        Z.B. so: ``status_func("Neuer Zwischenstand")``
    :param finished_func: Wird ausgeführt, wenn der Worker mit seiner Arbeit fertig ist.
        Es wird eine ausführbare Funktion oder None erwartet.
    """
    
    if not callable(status_func):
        status_func = None
    
    for i in range(start, stop, step):
        if status_func:
            status_func("Zwischenstand: %i" % i)
        time.sleep(0.6)
    
    if status_func:
        status_func("Fertig")
    
    if callable(finished_func):
        finished_func()


def main():
    # Testen
    
    def status_func(message):
        print message
    
    def finished_func():
        print "END"
    
    my_worker(2, 8, status_func = status_func, finished_func = finished_func)


if __name__ == "__main__":
    main()
example_gui.py:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx
from thread import start_new_thread
import example_lib


wx.SetDefaultPyEncoding("iso-8859-15")


class MyFrame(wx.Frame):
    
    def __init__(
        self, parent = None, title = "Example", size = wx.Size(550, 420)
    ):
        wx.Frame.__init__(self, parent, -1, title, size = size)
        
        panel = wx.Panel(self)
        
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
        
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox_main.Add(vbox, 1, wx.EXPAND | wx.ALL, 5)
        
        txt_status = wx.TextCtrl(
            panel, style = wx.TE_MULTILINE | wx.TE_AUTO_SCROLL | wx.TE_READONLY
        )
        vbox.Add(txt_status, 1, wx.EXPAND | wx.ALL)
        self.txt_status = txt_status
        
        btn_start = wx.Button(panel, label = "Start")
        vbox.Add(btn_start, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        btn_start.Bind(wx.EVT_BUTTON, self.start_worker)
        self.btn_start = btn_start
    
    
    def show_status(self, message):
        """
        Zeigt den neuen Status in der Textbox an.
        
        :param message: Dieser Text wird in der Textbox angehängt.
        """
        
        message = message.rstrip() + "\n"
        
        # Hier findet eine Trennung zwischen den Threads statt
        wx.CallAfter(self.txt_status.AppendText, message)
    
    
    def worker_finished(self):
        """
        Wird ausgeführt, wenn der Worker fertig ist. Damit könnte man den
        Start-Button wieder aktivieren, falls man diesen vorher deaktiviert
        hat.
        """
        
        # Hier findet eine Trennung zwischen den Threads statt
        wx.CallAfter(self.btn_start.Enable)
    
    
    def start_worker(self, event):
        """
        Startet den Worker aus dem Modul "example_lib".
        """
        
        # self.btn_start.Disable()
        
        self.txt_status.SetValue("")
        start_new_thread(
            example_lib.my_worker, (), 
            dict(
                start = 10, stop = 29, step = 2, status_func = self.show_status,
                finished_func = self.worker_finished
            )
        )


def main():
    """Testing"""
    app = wx.PySimpleApp()
    f = MyFrame()
    f.Center()
    f.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Danke!
So wie anderen Beispiel läuft es bei mir mittlerweile auch. Hoffentlich kriege ich den Rest jetzt auch noch hin :)
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Code: Alles auswählen

        start_new_thread(
            example_lib.my_worker, (),
            dict(
                start = 10, stop = 29, step = 2, status_func = self.show_status,
                finished_func = self.worker_finished
            )
        )
Was genau kann ich "start_new_thread" übergeben?

Angenommen my_worker befindet sich in der Klasse "MeineKlasse".
Könnte ich dann eine Instanz dieser Klasse erzeugen, dieser die Parameter (start =10...) übergeben und dann erst start_new_thread aufrufen?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

tomate hat geschrieben:Angenommen my_worker befindet sich in der Klasse "MeineKlasse".
Könnte ich dann eine Instanz dieser Klasse erzeugen, dieser die Parameter (start =10...) übergeben und dann erst start_new_thread aufrufen?
Hallo tomate!

Ja das funktioniert so. Aber das hättest du selber auch ausprobieren können. Und die Hilfe zu start_new_thread findest du hier: http://docs.python.org/lib/module-thread.html

In etwa so:

Code: Alles auswählen

class MeineKlasse(object):
    def __init__(self,...):
        ...
    def my_worker(self):
        ...

meine_klasseninstanz = MeineKlasse(start = 1, ...)

start_new_thread(meine_klasseninstanz.my_worker, ())
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
tomate
User
Beiträge: 48
Registriert: Sonntag 5. August 2007, 12:07

Es läuft.
Danke
Antworten