Threading für Anfänger

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Die Frage ist ja nur wie :-) Ich war ja auch schon so klug, und dachte daran in der For-Schleife ständig die url mit der get()-Methode abzufragen, aber da spielt Python nicht mit :-)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Trenne Deine Internetverbindung, starte eine Python-Shell und rufe ein ``requests.get()`` auf. Dann siehst Du doch, *welche* Exception von ``requests`` geworfen wird.

Ich wette da taucht ein ``ConnectionError`` auf - woher ich das weiß, verrate ich Dir anschließend! (Und nein, ich habe das jetzt nicht ausprobiert ;-) )
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Ich glaube, wir reden aneinander vorbei oder? :-)

Ich habe hier mal was auf die Schnelle zusammen gebastelt.

Code: Alles auswählen

            with open(self.location, 'wb') as fd:
                for chunk in file.iter_content(chunk_size):
                    fd.write(chunk)
                    downloaded_bytes = fd.tell()
                    print (float(downloaded_bytes)/file_size*100)
                    self.notify_progress.emit(float(downloaded_bytes)/file_size*100)
                        
                    if self._run_semaphore.available() == 0:
                        self._run_semaphore.release(1)
                        break
                    
                    try:
                        file = requests.get(self.url)           
                    except requests.exceptions.ConnectionError as ConnErr:
                        self.stop()
                        self.error_http.emit()
Mir geht es ja darum, dass während des Prozesses überwacht werden soll, ob die Verbindung steht oder nicht. Kann ja sein, dass sie mittendrin abbricht, und dann soll eine Messagebox ausgegeben werden, mit dem Hinweis, dass die Leitung gerade abgebrochen ist. Hier wird also im Schleifenkörper immer wieder versucht eine Verbindung zur url aufzubauen, und der mögliche Fehler wird dann abgefangen. Wenn ich das so umsetze, dann spielt Python nicht mit. Das heißt, die Datei wird erst gar nicht geladen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du hättest ja mal schreiben können, dass Dir die Ausnahme längst bekannt ist! :roll:

Und Du erwartest jetzt ernsthaft, dass man den Code so lesen kann? Der Name ``file`` gleich in Zeile 2 ist doch so gar nicht definiert - ich habe natürlich mal im Code zurückgeblättert und weiß nun, dass das ein HTTP-Response der ``requests.get``-Methode ist. Wie um alles in der Welt kommst Du dann zu einem Konstrukt rund um Zeile 13??? :shock: Du erwartest jetzt nicht ernsthaft, dass das irgend wie magisch funktioniert, oder? Nur mal so viel: Man sollte nicht den Ast absägen, auf dem man sitzt! Wenn man den Namen eines Objekts in einem Schleifenrumpg *neu* bindet (genau das macht Zeile 13 ja offensichtlich und trivial erkenntbar), dann hat das wohl sicherlich Auswirkungen auf eine andere Zeile (hier die 2), die auf das Objekt hinter diesem Namen zugreift. Du sendest also bei jedem Durchlauf einen *neuen* Request, was offenbar *nie* zu einem Ende führen kann... es sei denn dem Webserver reicht Deine DOS-Attacke irgend wann :twisted:

Ich kapiere auch so langsam nicht mehr, wieso Du alles immer so schrecklich kompliziert darstellst und Dich quasi nach Verrenkungen zu sehnen scheinst... vielleicht ist das noch ein Rest Masochismus von VB6? :twisted:

Es ist doch absolut klar, dass man die Ausnahme um das *eine* und *einzige* ``requests.get`` bauen muss! Also das, was Du uns hier im Snippet *nicht* zeigst, quasi Zeile 0 bzw. -1 oder so :P
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Hyperion: Ich glaube ja mittlerweile das mit dem VB können nicht mehr, denn selbst in VB kann man *dem* Verständnis von imperativem Programmablauf nichts sinnvolles auf die Beine stellen. Ich bin ja beim Bytes zählen ausgestiegen, aber das Problem hätte er doch in VB ganz genau so gehabt, denn das hat mit der konkreten Programmiersprache, solange sie imperativ ist, gar nichts zu tun. Wir haben hier jemandem dem absolute Grundlagen fehlen, insbesondere was programmieren allgemein angeht trotz angeblicher Vorkenntnisse, der mit Qt und nebenläufiger Programmierung anfängt um Code zum „in app update” einer GUI-Datenbankanwendung zu schreiben die es noch gar nicht gibt. Also weder den Datenbankteil noch die Geschäftslogik, dafür aber schon eine GUI wo gaaaanz wichtig ist das es ein MDI-Fenster ist. Das nenne ich mal Prioritäten setzen. ;-)

Ich versenke meine Zeit jedenfalls lieber in Shell-Skripte. ;-)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Das war nur ein Ausschnitt aus dem Quelltext, weil ich dachte, du hättest meinen Schnipsel noch parat. Wollte es dir irgendwie ersparen redundant meinen Quelltext zu präsentieren. Irgendwann hängt es dir aus den Ohren raus, also dachte ich, sei ich mal soo nett zu dir :-) Aber hier nochmal mein Quelltext in Paste bin.

Im überarbeitetem Quelltext siehst du in Zeilen 88-95 die URL-Anfrage im Try-Block verlegt wurde. Ich habe hier noch weitere Ausnahmen hinzugezogen. Die Ausnahmenbehanldungen funktionieren auch, nur das Problem ist einfach, dass sie nur einmal durchlaufen wird, und danach nicht mehr, es sei denn, die run()-Methode wird nochmal aufgerufen. Also wollte ich an den Schleifenkörper der For-Schleife ran. So wie ich dich verstanden habe, war meine Version total Humbug, und dafür sollte ich mich in die Ecke stellen :oops:.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@Sophus: Das sieht ein wenig nach Overkill aus! Du könntest eigentliche alle bekannten Exceptions bündeln und dann hast Du nur *einmal* den Emit-Aufruf. Zudem sind diese ganzen ``print``-Debug Ausgaben imho häßlich. Die kannst Du doch auch in einem Slot ausgeben, der sich an das Signal connected‽

Zeile 115 zweifel ich mal pauschal an, ohne es mir genauer angeguckt zu haben! Und über Zeile 114 haben wir ja zur Genüge gesprochen und davor gewarnt...

Irgend wie habe ich auch das Gefühl, dass einige Ausnhamen durchaus auch später auftreten könnten, also z.B. so etwas wie ``ReadTimeOut`` usw.

Ach ja, ich empfehle erneut mal "Clean Code" von Robert C. Martin. Dann wäre die Monsterfunktion ``run`` wohl nicht entstanden... von Zeile 86 - 137... ehrlich jetzt? Sind ja nur knappe 50 Zeilen :shock:
So etwas *musst* Du doch einfach irgend wie splitten!

Ich würde mein Fehler-Signal ja auch so parametrisieren, dass die HTTP-Fehlernummer und die Exception-Meldung selber nach außen emittiert werden können. Denn damit kannst Du dem Benutzer (oder einem Admin usw.) zusätzliche Infos geben, *woran* es gescheitert ist.

Ach ja, ceterum censeo ``check_folder_exists`` esse delendam! Selbiges gilt für diese ganzen häßlichen ``print``-Anweisungen... die machen das alles noch unschöner und unübersichtlicher.

@BlackJack: Hehe... ja, ich bin gerade so in einem Motivationsloch bezüglich Clojure und Scala. Ansonsten hätte ich am WE jetzt auch nicht so viel Zeit investiert... 8)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Ups, einiges habe ich im Quelltext vergessen rauszunehmen. Zum Beispiel das nackte Exception. Das mit den Print-Anweisungen hast du absolut Recht. Aber der Block sieht deswegen so "gewaltig" aus, weil die ganzen Exceptions dort untergebracht wurden. Die eigentliche Funktion, also das Verbindung zum Webserver und Herunterladen der Datei ist ja nach wie vor klein. Aber dennoch komme ich nicht dahinter, wie ich die ConnectionError im Schleifenkörper abfragen soll, ohne dabei den Try-Block zu benutzen, also zu meiner vorherigen Version, die natürlich fehlerhaft war.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Warum nutzt Du nicht die Qt-Beispiele? Was Du hier als eckiges Rad nachbaust, gibts als fertiges Beispiel mit Qt-Boardmitteln: http://doc.qt.io/qt-5/qtnetwork-http-example.html

Natürlich kann es Sinn machen, dass lieber mit Python-Bibliotheken umsetzen zu wollen. Allerdings fehlen Dir hierfür Grundlagen, um das mal eben selbst entwickeln zu können. Daher mein Rat - halte Dich an Bsp. (gibts für requests sicher auch)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Das Herunterladen der Datei ist ja nicht das Problem, sondern die ständige Überwachung, ob die Verbindung besteht. Und das Beispiel, welches du mir da präsentierst ist in C++ geschrieben. Damit kann ich nun nichts anfangen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sophus hat geschrieben:Und das Beispiel, welches du mir da präsentierst ist in C++ geschrieben. Damit kann ich nun nichts anfangen.
Um zu erkennen, was das Ding tut, muss man nun wahrlich kein C++ können! Die Strukturen sind doch gerade im Qt-GUI Bereich ziemlich identisch... ob das die eingebaute Qt-Funktionalität da evtl. etwas besser kapselt, kann ich nicht beurteilen.

Und ehrlich gesagt weiß ich nicht mehr, wo noch ein Problem liegt? Du solltest da ein wenig kürzen usw. aber ansonsten ist doch alles klar dachte ich?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: ehrlich gesagt sehe ich nicht, wo es ein Problem geben könnte. Solange die Schleife läuft und Daten heruntergeladen werden, besteht eine Verbindung, sobald die Verbindung abbricht, meldet requests eine Exception. Wo muß man da groß was ständig überwachen?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3 und Hyperion: Das Problem ist, das das requests-Modul eben keine Ausnahmen anzeigt, sobald die Verbindung unterbrochen wird. Um mich an meinem Quelltext zu halten, damit wir wissen, wovon ich rede: In Zeile 88 bis 95 wird einmal beim Aufruf der run()-Funktion geschaut bzw. versucht eine Verbindung aufzubauen. Soweit alles super. Nun muss im Schleifenkörper der For-Schleife sicherlich zusätzlich eine Routine eingebaut werden, welches die Verbindung überwacht. Der vorliegende Quelltext meldet keine Ausnahme, wenn ich mitten im Prozess die Internet-Verbindung absichtlich trenne.

Ich war ja auch so schlau und dachte mir, ich setze im vorliegenden Quelltext zwischen Zeile 134 und 136 folgende Zeile:

Code: Alles auswählen

                    if not file.reason == "OK":
                        break
So das jedesmal abgefragt wird, ob die reason()-Methode ein OK beinhaltet, der vom Webserver geliefert wird. Wenn die Verbindung ja abgebrochen wird, kann die reason()-Methode schlecht ein OK bekommen. Aber selbst diese Überlegung will mir nicht ganz gelegen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

einige von euch erinnern sich an mein kleines Beispiel, um eine Datei herunterzuladen - das Ganze im Zusammenspiel von QThread und QProgressbar. Hier nochmal mein ausführbares Beispiel auf meinem github-Konto. Dieses Beispiel funktioniert auch ganz gut.

Nun wollte ich dazu übergehen, und dieses Beispiel in meine vorhandene Klasse einbetten. Dazu hier der Quelltext - ebenfalls auf github. Wir sehen in Zeile 90-94, dass die Download_Thread()-Klasse wie im ausführbaren Beispiel (Zeile 61-64) instanziiert und mit Funktionen verbunden werden. Weiterhin sehen wir, dass ich nahezu alle Funktionen aus dem ausführbaren Beispiel übernommen habe, also: on_finish_download(), on_HTTPError(), on_progress(), set_progressbar(), check_folder_exists(), on_finished() und on_start(). Das einzige was nicht aus dem ausführbaren Beispiel-Quelltext entnommen und integriert wurde, ist Zeile 179-184.

Ich bekomme auch keinerlei Fehlermeldung, jedoch beginnt das Herunterladen auch nicht. Um zu überprüfen, ob die Daten korrekt übergeben werden, seht ihr, dass ich im zweiten Quelltext in Zeile 181-183 eine Print-Anweisung eingebaut habe. Dies wird auch über die Konsole korrekt angezeigt, jedoch startet das Laden nicht.


Ich glaube, ich konnte das Problem etwas eingrenzen. Ich beziehe mich auf den zweiten Quelltext:

Code: Alles auswählen

    def run(self):
        print "LOCATION", self.location_file #<-- wird ausgegeben
        print "URL", self.url_download #<-- wird ausgegeben
        try:
             print "Connect to the url-link" #<-- wird ausgegeben
             getfile = requests.get(self.url_download, stream=True)
             web_status = getfile.status_code
             print "Status: ", web_status #<-- wird nicht ausgegeben
[...]
Habe ich hier etwas übersehen? Das heißt, er kann keine Verbindung zum Webserver aufbauen, wenn schon im Try-Block keine Ausgabe über den status_code gegeben wird?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich habe weiterhin rumprobiert: Wenn ich also mein ausführbares Beispiel allein starte, so dass diese Zeile greift:

Code: Alles auswählen

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(600, 400)
    window.show()
    sys.exit(app.exec_())
Dann funktioniert mein ausführbares Beispiel. Binde ich aber dieses Beispiel direkt ein, so dass die oben genannten Zeilen nicht greifen, da es nicht als Main() läuft, sondern von meinem Projekt aus, dann funktioniert das ausführbares Beispiel auch nicht. Und ich komme einfach nicht dahinter, wieso das es nicht klappen will :K
Antworten