Seite 1 von 1

Wartemeldung - Thread

Verfasst: Montag 6. November 2006, 18:05
von JR
Hallo Pythoner!

In meiner Applikation kann ich einen Dialog rufen, in dessen Init eine MySQL-Abfrage übers Internet gestartet wird. Und hier liegt mein Problem. Die Abfrage dauert etwa 2 bis 4 Sekunden. Dementsprechend poppt der Dialog auch erst nach einer kurzen Warte zeit auf.

Ich würde währenddessen gerne eine asynchrone Meldung wie "Daten werden geladen..." bringen.

Was muss ich um meinen Funktionsaufruf, welcher mir die abgefragten Daten zurückgibt herumbauen, damit das klappt?

Nach dem Motto:

Öffne Wartemeldung
Rufe Funktion die Daten ruft
Schließe Wartemeldung

Geht das mit wx.Thread() ?

Viele Grüße
JR

Re: Wartemeldung - Thread

Verfasst: Montag 6. November 2006, 19:41
von gerold
JR hat geschrieben:Öffne Wartemeldung
Rufe Funktion die Daten ruft
Schließe Wartemeldung
Hi JR!

So könnte es ohne Threads funktionieren:

Code: Alles auswählen

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

import wx
import time

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


class MyDialog(wx.Dialog):
    
    def __init__(self, parent = None, id = -1, title = "Hallo"):
        wx.Dialog.__init__(self, parent, id, title)
        
        statuslabel = wx.StaticText(self, -1)
        self.statuslabel = statuslabel
        
        wx.CallAfter(self.ich_tu_was)


    def ich_tu_was(self):
        statuslabel = self.statuslabel
        for i in range(1, 11):
            statuslabel.SetLabel(str(i))
            wx.YieldIfNeeded()
            time.sleep(0.5)


def main():
    app = wx.PySimpleApp()
    diag = MyDialog()
    diag.ShowModal()


if __name__ == "__main__":
    main()
Und so funktioniert es mit Thread:

Code: Alles auswählen

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

import wx
import time
from thread import start_new_thread

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


class MyDialog(wx.Dialog):
    
    def __init__(self, parent = None, id = -1, title = "Hallo"):
        wx.Dialog.__init__(self, parent, id, title)
        
        statuslabel = wx.StaticText(self, -1)
        self.statuslabel = statuslabel
        
        start_new_thread(self.ich_tu_was, ())


    def ich_tu_was(self):
        statuslabel = self.statuslabel
        for i in range(1, 11):
            wx.CallAfter(statuslabel.SetLabel, str(i))
            wx.YieldIfNeeded()
            time.sleep(0.5)


def main():
    app = wx.PySimpleApp()
    diag = MyDialog()
    diag.ShowModal()


if __name__ == "__main__":
    main()
Es gibt mehrere Möglichkeiten. Du könntest, unter anderem, auch eine Progressbar, einen ProgressDialog oder einen Throbber anzeigen/einblenden.

lg
Gerold
:-)

Edit: Code verbessert.

Verfasst: Montag 6. November 2006, 19:58
von JR
Hi Gerold!

Zunächst einmal vielen Dank für die Codebeispiele.

Habe sie mir eben durchgelesen und weiß nicht, ob das so klappen kann.

Im Prinzip habe ich in der Funktion ich_tu_was einfach nur einen MySQL-Abfragestring den ich über die MySQL-Schnittstelle absende.
Diese Abfrage dauert lange.
Das heißt, die MySQL-Abfrage und die Funktion ich_tu_was müssten gleichzeitig laufen und wenn die Abfrage zu Ende ist, müsste die Schleife abgebrochen werden.

Ich versuchs mal
Grüße
Jamil

Verfasst: Montag 6. November 2006, 20:53
von gerold
JR hat geschrieben:und wenn die Abfrage zu Ende ist, müsste die Schleife abgebrochen werden
Hi JR!

Hier ist noch ein Beispiel. Allerdings spiele ich mich darin mit Threads und das ist für den Anfang nicht immer einfach.

Das Wichtigste ist, dass du nichts was die grafische Oberfläche ändert oder ändern kann, in einem Thread machst. Wenn sich etwas an der GUI ändern soll, dann übergebe den Auftrag an den Hauptthread, in dem das Programm selber läuft.

Das geht am Einfachsten mit ``wx.CallAfter()``.

Code: Alles auswählen

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

import wx
import time
from thread import start_new_thread


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


class MyDialog(wx.Dialog):
    
    def __init__(self, parent = None, id = -1, title = "Hallo"):
        
        # Dialogfenster initialisieren
        wx.Dialog.__init__(self, parent, id, title)
        
        # Abbruchvariable vorbereiten
        self.cancel = False
        
        # Einfaches Label für Statusanzeige
        statuslabel = wx.StaticText(self, -1, "Gleich tut er etwas...")
        self.statuslabel = statuslabel
        
        # Neuen Thread starten. In diesem Thread wird gearbeitet und alles
        # was irgendwie an den Hauptthread übergeben wird, wird über ``wx.CallAfter``
        # geleitet.
        # Alles was irdengetwas mit der GUI zu tun hat, hat im Hauptthread zu bleiben.
        # Deshalb wird alles, was die Anzeige verändert, per ``wx.CallAfter`` an
        # den Hauptthread weitergeleitet und nicht diret in einem anderen Thread
        # geändert.
        start_new_thread(self.ich_tu_was, ())
    
    
    def show_progressdiag(self):
        
        # ProgressDialog anzeigen
        diag = wx.ProgressDialog(
            "Arbeitsmeldung", "Jetzt tu ich etwas...", 100, self
        )
        
        # In einer Schleife die Progressbar bewegen.
        while True:
            time.sleep(0.1)
            diag.UpdatePulse()
            # Wenn die Abbruchvariable gesetzt wurde --> Fertig
            if self.cancel:
                diag.Destroy()
                return
    

    def ich_tu_was(self):
        statuslabel = self.statuslabel
        
        # Label setzen (über Threadgrenzen hinweg -- deshalb mit CallAfter)
        wx.CallAfter(statuslabel.SetLabel , "Jetzt tu ich etwas...")
        wx.CallAfter(wx.YieldIfNeeded)
        
        # Abbruchvariable auf False setzen
        self.cancel = False
        
        # Den Progressdialog im Hauptthread starten.
        # (über Threadgrenzen hinweg -- deshalb mit CallAfter)
        wx.CallAfter(self.show_progressdiag)
        
        # Hier wird gearbeitet
        time.sleep(5) # Hier könntest du die SQL-Abfrage durchführen
        
        # Abbruchvariable setzen, damit der PrograssDialog endet
        self.cancel = True
        
        # Label setzen (über Threadgrenzen hinweg -- deshalb mit CallAfter)
        wx.CallAfter(statuslabel.SetLabel , "Fertig!")
        wx.CallAfter(wx.YieldIfNeeded)


def main():
    app = wx.PySimpleApp()
    diag = MyDialog()
    diag.ShowModal()


if __name__ == "__main__":
    main()
lg
Gerold
:-)

Verfasst: Montag 6. November 2006, 21:01
von JR
Hi Gerold!

Deine Mühen sind wirklich toll.
Also ich habe wohl genau den klassischen Fehler gemacht und im Thread meinen "Bitte warten..." Dialog gestartet. Ging auch halbwegs schief :-)

Baue jetzt deine letzte Variante ein und melde mich dann bestimmt glücklich zurück...

"Bitte warten..."

Gruß
Jamil

Verfasst: Montag 6. November 2006, 22:58
von JR
Hi Gerold,

also es ist doch etwas komplizierter.
Erst einmal habe ich gemerkt, wieviel Performance es schluckt, wenn man mehrere MySQL-Abfragen durch eine Schleife hat.
Konnte eine Schleife mit vielen Abfragen durch eine sinnvollere Abfrage ersetzen und benötige an dieser Stelle keine "Bitte warten..." Meldung mehr.

Dennoch gibt es andere Stellen, wo die Abarbeitung solch eine Meldung parallel bringen sollte, damit meine Anwender nicht denken, dass das Programm abgeschmiert ist.
Ich muss mich mit dem Threading mehr auseinandersetzen. Mein Problem ist, dass ich versucht habe, über den Thread ganze Methoden (inkl. Rückgabewert und GUI-Steuerung) auszulösen. Das ging aber alles nicht so, wie ich es wollte. Entweder kam nur ein weißer Dialog oder meine Stelle, wo ich das Abbruchkriterium für die Progressschleife setze, wurde nicht durchlaufen etc.

Also ich werde es einfach mal die Tage versuchen, hinzubekommen.

Vielen Dank jedenfalls nochmals für deine rasche Reaktion.

Viele Grüße
Jamil

Verfasst: Donnerstag 7. Dezember 2006, 18:27
von JR
Hallo!

Keine Frage, sondern jetzt im Folgenden der Code dessen, was ich gesucht habe.
Ist im Prinzip komplett die obige Lösung von Gerold, wobei in den Zeilen 82-85

irgendetwas passiert, was halt länger dauern kann.

Code: Alles auswählen

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

import wx 
import time 
from thread import start_new_thread 


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


class MyDialog(wx.Dialog): 
    
    def __init__(self, parent = None, id = -1, title = "Hallo"): 
        
        # Dialogfenster initialisieren 
        wx.Dialog.__init__(self, parent, id, title) 
        
        # Abbruchvariable vorbereiten 
        self.cancel = False 
        
        # Einfaches Label für Statusanzeige 
        statuslabel = wx.StaticText(self, -1, "Gleich tut er etwas...") 
        self.statuslabel = statuslabel 
        
        # Neuen Thread starten. In diesem Thread wird gearbeitet und alles 
        # was irgendwie an den Hauptthread übergeben wird, wird über ``wx.CallAfter`` 
        # geleitet. 
        # Alles was irdengetwas mit der GUI zu tun hat, hat im Hauptthread zu bleiben. 
        # Deshalb wird alles, was die Anzeige verändert, per ``wx.CallAfter`` an 
        # den Hauptthread weitergeleitet und nicht diret in einem anderen Thread 
        # geändert. 
        start_new_thread(self.ich_tu_was, ()) 
    
    
    def show_progressdiag(self): 
        
        # ProgressDialog anzeigen 
        diag = wx.ProgressDialog(title="Arbeitsmeldung", 
                                 message="Jetzt tu ich etwas...", 
                                 maximum=100, parent=self,
                                 style=wx.PD_APP_MODAL|
                                 wx.PD_CAN_ABORT|
                                 wx.PD_SMOOTH^
                                 wx.PD_AUTO_HIDE) 
        self.diag = diag
        # In einer Schleife die Progressbar bewegen.
        i = 0
        while True: 
            time.sleep(0.5)
            if i * 10 >= 100:
                i=0
            
            try:             
                self.diag.Update(i * 10)
            except:
                pass
            i += 1

            # Wenn die Abbruchvariable gesetzt wurde --> Fertig
            if self.cancel: 
                diag.Destroy() 
                return
             
    

    def ich_tu_was(self): 
        statuslabel = self.statuslabel 
        
        # Label setzen (über Threadgrenzen hinweg -- deshalb mit CallAfter) 
        wx.CallAfter(statuslabel.SetLabel , "Jetzt tu ich etwas...") 
        wx.CallAfter(wx.YieldIfNeeded) 
        
        # Abbruchvariable auf False setzen 
        self.cancel = False 
        
        # Den Progressdialog im Hauptthread starten. 
        # (über Threadgrenzen hinweg -- deshalb mit CallAfter) 
        wx.CallAfter(self.show_progressdiag) 
        
        # Hier wird gearbeitet
        for i in range(10):
            time.sleep(1) # Hier könntest du die SQL-Abfrage durchführen
            print i
        
        # Abbruchvariable setzen, damit der PrograssDialog endet 
        self.cancel = True 
        
        # Label setzen (über Threadgrenzen hinweg -- deshalb mit CallAfter) 
        wx.CallAfter(statuslabel.SetLabel , "Fertig!") 
        wx.CallAfter(wx.YieldIfNeeded) 


def main(): 
    app = wx.PySimpleApp() 
    diag = MyDialog() 
    diag.ShowModal() 


if __name__ == "__main__": 
    main()
Viele Grüße
Jamil