Reputation:
I'm using Python 3.7.6 with PyQt5 on my Windows 10 computer. I am trying to write a simple application that will run three different procedures at the same time showing the output in three separate text boxes in the same window. I have tried to create some simple base code to add to, but am having an issue using the threading module with PyQt5. Here is my code:
import sys, time
from threading import Thread
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QPlainTextEdit, QHBoxLayout
)
def run1():
for i in range(20):
text_1.setPlainText(text_1.toPlainText() + (f"{i}\n"))
time.sleep(0.0125)
def run2():
for i in range(20):
text_2.setPlainText(text_2.toPlainText() + (f"{i}\n"))
time.sleep(0.0125)
def run3():
for i in range(20):
text_3.setPlainText(text_3.toPlainText() + (f"{i}\n"))
time.sleep(0.0125)
app = QApplication([sys.argv])
win = QMainWindow()
text_1 = QPlainTextEdit()
text_2 = QPlainTextEdit()
text_3 = QPlainTextEdit()
my_widget = QWidget()
my_widget.layout = QHBoxLayout()
my_widget.layout.addWidget(text_1)
my_widget.layout.addWidget(text_2)
my_widget.layout.addWidget(text_3)
my_widget.setLayout(my_widget.layout)
win.setCentralWidget(my_widget)
t1 = Thread(target=run1)
t2 = Thread(target=run2)
t3 = Thread(target=run3)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
win.show()
sys.exit(app.exec_())
When I run this code, it shows the desired output, but with multiple instances of the following error:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x166abf795e0), parent's thread is QThread(0x166a9bb0fb0), current thread is QThread(0x166abf56000)
I think I know why this is happening, but do not know how to fix it. I presume that I should use PyQt5's own QThread class, but cannot get my head around how to do it. Currently, I will just run my three separate text based Python applications with each showing its output in its own window, but I would prefer a single GUI based application with all three incorporated.
Upvotes: 1
Views: 1248
Reputation: 243945
The problem has nothing to do with the use of QThread or not. The problem is that the GUI elements (for example the QWidget, QTextDocument, etc) are not thread-safe so you should not modify them or create them in a different thread than the main one. To emphasize my initial comment in my solution I will not use QThread but I will continue using threading but I will send the information to the main thread through signals(what if they are thread-safe):
import sys, time
from threading import Thread
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QPlainTextEdit,
QHBoxLayout,
)
class Worker(QObject):
messageChanged = pyqtSignal(str)
def start(self, fn):
Thread(target=self._execute, args=(fn,), daemon=True).start()
def _execute(self, fn):
fn(self)
def write(self, message):
self.messageChanged.emit(message)
def run1(worker):
for i in range(20):
worker.write(f"{i}\n")
time.sleep(0.0125)
def run2(worker):
for i in range(20):
worker.write(f"{i}\n")
time.sleep(0.0125)
def run3(worker):
for i in range(20):
worker.write(f"{i}\n")
time.sleep(0.0125)
app = QApplication([sys.argv])
win = QMainWindow()
text_1 = QPlainTextEdit()
text_2 = QPlainTextEdit()
text_3 = QPlainTextEdit()
my_widget = QWidget()
my_widget.layout = QHBoxLayout()
my_widget.layout.addWidget(text_1)
my_widget.layout.addWidget(text_2)
my_widget.layout.addWidget(text_3)
my_widget.setLayout(my_widget.layout)
win.setCentralWidget(my_widget)
worker1 = Worker()
worker1.messageChanged.connect(text_1.appendPlainText)
worker2 = Worker()
worker2.messageChanged.connect(text_2.appendPlainText)
worker3 = Worker()
worker3.messageChanged.connect(text_3.appendPlainText)
worker1.start(run1)
worker2.start(run2)
worker3.start(run3)
win.show()
sys.exit(app.exec_())
Upvotes: 1