Thread Problem

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Hallo erst einmal an alle hier!
Ich bin gerade dabei mir PyQt und Sockets anzuschauen um einen kleinen Chat zu programmieren.
Außerdem wollte ich gerade das erste Mal Threads verwenden.
Das ist mein vorläufiger Code:

Code: Alles auswählen

class MyForm(QtGui.QMainWindow):
	def __init__(self):
		_thread.start_new_thread(self.get_messages, ())
		QtGui.QWidget.__init__(self)
		self.ui = Ui_Form()
		self.ui.setupUi(self)
		QtCore.QObject.connect(self.ui.button, QtCore.SIGNAL("clicked()"), self.send)
		
	def get_messages(self):
		while(True):
			response = s.recv(1024)
			self.ui.textBrowser.setPlainText(str(response, "utf-8"))
	
	def send(self):
		data = self.ui.input.text()
		s.sendall(bytes(data, "utf-8"))
Nun kommt aber diese Fehlermeldung:

Code: Alles auswählen

QObject: Cannot create children for a parent that is in a different thread.
Die Fehlermeldung ist mir auch verständlich aber kann mir jemand bitte eine Idee liefern wie ich das sonst lösen soll?
Danke schon einmal.
Zuletzt geändert von Anonymous am Freitag 4. Januar 2013, 16:14, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

1. GUIs und Multithreading vertragen idR. sich nur, sofern man die Multithreading-Mechanismen des GUI-Frameworks verwendet. Also nicht _thread. Verwende stattdessen QtCore.QThread().

2. Warum - oh, warum nur? - das Low-Level-Modul _thread? Der Name beginnt mit einem Unterstrich, was in Python bedeutet: Bitte verwende das nur sofern du genau weißt, was du tust. Und den Eintrag zum High-Level-Modul threading findet man, wenn man den Index zur Standard-Lib von oben nach unten durchliest, vor dem Eintrag zu _thread. Irgendwie scheint _thread Multithreading-noobs magisch anzuziehen.
In specifications, Murphy's Law supersedes Ohm's.
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Danke für eure schnelle Antworten.
Ok dann verwende ich ab jetzt QThread.
Aber irgendwie verstehe ich noch überhaupt nicht wie das nun funktionieren soll.
Ich habe jetzt einmal eine Worker-Thread Klasse wie aus dem Wiki-Link. Aber wie kann ich von dort aus auf mein UI zugreifen?
Also das ist jetzt mein Code und mir ist selbst klar das das nicht funktionieren kann.

Code: Alles auswählen

class Worker(QtCore.QThread):
	def __init__(self, parent = None):
		QtCore.QThread.__init__(self, parent)
		self.exiting = False
		while(True):
			response = s.recv(1024)
			self.ui.textBrowser.setPlainText(str(response, "utf-8"))

class MyForm(QtGui.QMainWindow):
	def __init__(self):
		QtGui.QWidget.__init__(self)
		self.thread = Worker()
		self.ui = Ui_Form()
		self.ui.setupUi(self)
		QtCore.QObject.connect(self.ui.button, QtCore.SIGNAL("clicked()"), self.send)
	
	def send(self):
		data = self.ui.input.text()
		s.sendall(bytes(data, "utf-8"))
Aber ich verstehe nicht wieso nun gar kein Gui mehr angezeigt wird.
Und noch zu meiner Entschuldigung ich fand auch Threading aber dachte mir, dass es vielleicht klüger ist _thread zu verwenden weil ich auch von OOP noch nichts verstehe :K
Die Hilfe hier ist jedenfalls wirklich super.
Danke.
Zuletzt geändert von Anonymous am Freitag 4. Januar 2013, 16:15, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
lunar

@prog(r)amer Von OOP nichts zu verstehen, ist eine denkbar ungünstige Voraussetzung für GUI-Programmierung. Zumindest grundlegendes Verständnis von Objektorientierung ist eine absolut notwendige Grundlage.

Bezüglich des konkreten Problems: Die Kommunikation zwischen GUI und Thread läuft nurmehr indirekt über Signale und Slots. Dein Thread deklariert ein Signal, und sendet dieses aus, sobald der Oberfläche etwas mitteilen möchte. Die Oberfläche behandelt diese Mitteilung dann in einem entsprechenden Slot. Der direkte Zugriff auf "textBrowser" im "Worker"-Thread ist also ebenso falsch wie Deine vorherigen Versuche mit "_thread".

Lese die Qt-Dokumentation über Signale und Slots und über Threading, dazu auch die PyQt-Dokumentation zu Signalen und Slots.
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Jetzt habe ich versucht, nicht direkt sondern in meinem Hauptthread über eine update-Funktion die Antwort hinzuzufügen.

Code: Alles auswählen

class Worker(QtCore.QThread):
	def __init__(self, parent = None):
		QtCore.QThread.__init__(self, parent)
	def run(self):
			while(True):
				response = s.recv(1024)
				self.emit(QtCore.SIGNAL('response( QString )'), response)
			

class MyForm(QtGui.QMainWindow):
	def __init__(self):
		QtGui.QWidget.__init__(self)
		self.thread = Worker()
		self.ui = Ui_Form()
		self.ui.setupUi(self)
		QtCore.QObject.connect(self.ui.button, QtCore.SIGNAL("clicked()"), self.send)
		QtCore.QObject.connect(self.thread,QtCore.SIGNAL("response( QString )"), self.update)
		
		
	def update(self, response):
		self.ui.textBrowser.setPlainText(str(response, "utf-8"))
	
	def send(self):
		data = self.ui.input.text()
		s.sendall(bytes(data, "utf-8"))
Allerdings scheine ich irgendwas falsch zu machen, da meine Updatefunktion nicht aufgerufen wird. Ich fürchte es hat etwas mit dem Code in meiner Worker Klasse zu tun. Kann mir jemand sagen wo konkret der Fehler liegt?
BlackJack

@prog(r)amer: Wo startest Du denn den Thread?
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Der Thread wird doch durch

Code: Alles auswählen

self.thread = Worker()
gestartet?
Oder mach ich irgendwas falsch?
lunar

@prog(r)amer Nein, dadurch wird der Thread lediglich erzeugt. Starten musst Du ihn explizit. Wie das geht, steht in der Dokumentation…
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@lunar: Du hast "vergessen", dem OP den besten Beispielcode ever zu posten :mrgreen:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
lunar

@Hyperion :) Ich habe allerdings offen gesagt Zweifel, dass dieses Beispiel dem OP hilft…
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Hyperion: das ist ja schlimmer wie an Ostern. Bis ich den counter.start()-Aufruf gefunden habe,
mußte ich das Beispiel dreimal durchlesen. :x
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Sorry ich scheine wirklich sehr unfähig zu sein ):
In der Dokumentation hab ich jetzt noch einmal nach geschaut, aber unter dem Threading Link habi ich nur von irgendwas mit exec gelesen, nichts von start. (wahrscheinlich bin ich blind auch noch)
Naja, jetzt hab ich das geändert, und einfach einmal ausprobiert ob er einen Text in der Konsole ausgibt wenn ich in in die Endlosschleife gebe.
Das hat er auch getan.
Aber irgendwie scheint er mein Signal nicht zu mögen?

Code: Alles auswählen

class Worker(QtCore.QThread):
	def run(self):
			while(True):
				response = s.recv(1024)
				self.emit(QtCore.SIGNAL('response( QString )'), response)
			

class MyForm(QtGui.QMainWindow):
	def __init__(self):
		QtGui.QWidget.__init__(self)
		self.thread = Worker()
		self.thread.start()
		self.ui = Ui_Form()
		self.ui.setupUi(self)
		QtCore.QObject.connect(self.ui.button, QtCore.SIGNAL("clicked()"), self.send)
		QtCore.QObject.connect(self.thread,QtCore.SIGNAL("response( QString )"), self.update)
		
	def update(self, response):
		self.ui.textBrowser.setPlainText(str(response, "utf-8"))
	
	def send(self):
		data = self.ui.input.text()
		s.sendall(bytes(data, "utf-8"))
Danke, das thread.start() hat mir jetzt wirklich weitergeholfen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sirius3 hat geschrieben:@Hyperion: das ist ja schlimmer wie an Ostern.
*als* :twisted:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Vielen Dank nocheinmal, dank euch hab ich das kleine Programm jetzt doch noch zum Laufen gebracht.
Und Schuld war, dass ich dem QString Signal ein Byte Objekt gegeben hab.
BlackJack

@prog(r)amer: Ich würde auf die „neue” Art Signale und Slots verbinden umstellen und das `response`-Signal in der `Worker`-Klasse definieren. „Neue” in Anführungsstrichen, weil das eigentlich gar nich mehr so neu ist. Eine Umstellung bringt weniger Schreibarbeit und mehr Typsicherheit, dass heisst Fehler fallen früher auf. Wobei Du selbst für die alte Variante wohl das umständlichste und längste genommen hast, was man da schreiben kann.

Der Quelltext entspricht in der Form nicht ganz dem Style Guide for Python Code (PEP 8). Bei der Namensgebung ist es üblich die gepflogenheiten von GUI-Toolkits zu übernehmen, aber die Einrückung und Leerzeichensetzung kann man auch da vom Guide übernehmen.

``while`` ist keine Funktion, deshalb sollte man es auch nicht so schreiben als wäre es eine. Die Klammern sind in Python an der Stelle sowieso überflüssig.

Der `Worker` wird erstellt und gestartet bevor die GUI aufgesetzt und die Signale verbunden sind. Da können also potentiell Daten verloren gehen.

Wo kommt `s` eigentlich her? Das sieht nach einer globalen Variablen aus, was ziemlich unschön/unsauber wäre.

Letztendlich kann man hier auch ohne eigene Threads auskommen wenn man die Netzwerkklassen von Qt verwendet. `QTcpSocket` hat zum Beispiel ein Signal das
gesendet wird wenn Daten zum Lesen bereit stehen. Es gibt eine Übersicht zu Netzwerkklassen in der Qt-Dokumentation.
prog(r)amer
User
Beiträge: 10
Registriert: Freitag 4. Januar 2013, 12:57

Ja `s` ist eine globale Variable. Wo soll ich `s` denn sonst definieren?
Den Rest hab ich umgestellt.
BlackJack

@prog(r)amer: Wenn Objekte auf andere Zugriff haben müssen, dann in der Regel dadurch, dass sie das andere Objekt als Attribut haben oder es beim Methodenaufruf als Argument übergeben bekommen. Also könnte man das der GUI-Klasse bei der Erstellung übergeben und das Objekt gibt es dann an den `Worker` weiter. Oder man übergibt es dem `Worker` und *den* dann der GUI-Klasse. Letztendlich würde ich zusehen Programmlogik und GUI nicht so eng zu vermischen.
Antworten