简体   繁体   中英

Python PyQt5 threading QObject: Cannot create children for a parent that is in a different thread

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.

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_())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM