PyQt5和QML多线程实例
Python和QML编程中,选取了moveToThread尝试实现QML界面的多线程操作。
参考网上的一个QT QML的moveToThread实例,起初将其C++源码改写为Python结合Pyside2形式,但是当真正运行起来的时候,QML的UI界面陷入卡顿;以致我怀疑C++改写出来的Python代码是不是不可行,或者所调用的Qthread,QtcpSocket应当采用Python的原有对应的模块,经过不断尝试,似乎问题出在QML这里,因为如果采用Qwidget编写的GUI来运行则正常运行。
我在QT的官网进行浏览的时候,发现PySide2 5.15的一个Bug修复记录是关于Qthread线程QML界面卡顿的;当时对应的这个Bug描述提到PySide2运行卡顿,而改用PyQt5则正常。
我当前的PySide2版本就是已修复上面这一Bug后的了,不过由此看来似乎PySide2还是有不足之处,因此我决定也尝试改用PyQt5给QML最后一次机会,很幸运这一次改成PyQt5后运行流畅, 看来真的是PySide2存在的问题导致曾经的我陷入了困境之中吧,接下来我将继续采用PyQt5+QML进行更多尝试了。
下面是对比的代码。
分别采用PySide2和PyQt5的方式打开同一QML界面,PySide2运行卡顿,PyQt5则运行流畅。
#moveToThread_main_pyqt5.py
# -*- coding: utf-8 -*-
from PyQt5.QtQml import qmlRegisterType,QQmlApplicationEngine
from PyQt5.QtQuick import QQuickView
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtGui import QColor, QGuiApplication, QPainter, QPen
from PyQt5.QtQml import qmlRegisterType
from PyQt5.QtQuick import QQuickPaintedItem, QQuickView
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal,pyqtSlot,pyqtProperty,QObject, QThread,QUrl,QSettings,QRectF, Qt
from PyQt5.QtNetwork import QTcpSocket
from QtUi import Ui_MainWindow
import sys
import time
class PieChart(QQuickPaintedItem):
chartCleared = pyqtSignal() #定义信号
@pyqtProperty(str)
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
@pyqtProperty(QColor)
def color(self):
return self._color
@color.setter
def color(self, color):
self._color = QColor(color)
def __init__(self, parent=None):
super(PieChart, self).__init__(parent)
self._name = ""
self._color = QColor()
def paint(self, painter):
painter.setPen(QPen(self._color, 2))
painter.setRenderHints(QPainter.Antialiasing, True)
rect = QRectF(0, 0, self.width(), self.height()).adjusted(1, 1, -1, -1)
painter.drawPie(rect, 90*16, 290*16)
@pyqtSlot()
def clearChart(self):
self.color = QColor(Qt.transparent)
self.update()
self.chartCleared.emit()
class Work2(QObject):
count = int(0)
count_signal = pyqtSignal(int)
signalDataReceive = pyqtSignal(str)
def __init__(self):
super().__init__()
self.m_socket = QTcpSocket()
self.run = True
@pyqtSlot()
def tcpWork(self):
print('hello tcpwork')
self.m_socket = QTcpSocket()
self.m_socket.connectToHost("127.0.0.1",8000)
if self.m_socket.waitForConnected(-1):
while True:
# tm = self.m_socket.waitForReadyRead()
# print('tm',tm)
# tm = self.m_socket.waitForReadyRead()
# print('tm2',tm)
if self.m_socket.waitForReadyRead():
# tm = False
res = self.m_socket.readAll()
self.msg = str(res.data())
# if self.msg != b'':
print('recv:',self.msg)
# time.sleep(0.001)#1ms
self.signalDataReceive.emit(self.msg)
def work(self):
self.run = True
while self.run:
print(str(self.count))
self.count += 1
self.count_signal.emit(self.count)
time.sleep(1)
def work_stop(self):
self.run = False
class Work(QObject):
count = int(0)
count_signal = pyqtSignal(int)
def __init__(self):
super(Work, self).__init__(parent)
self.run = True
def work(self):
self.run = True
while self.run:
print(str(self.count))
self.count += 1
self.count_signal.emit(self.count)
time.sleep(1)
def work_stop(self):
self.run = False
# class TcpThread2(QObject):
# chartCleared = pyqtSignal() #定义信号
# def __init__(self,parent=None):
# super(TcpThread2,self).__init__(parent)
# self.thread = QThread()
# self.worker = Work2()
# # def __init__(self, parent=None):
# # super(TcpThread2, self).__init__(parent)
# self._name = ""
# self._color = QColor()
class TcpThread2(QObject):
dataChanged = pyqtSignal()
def __init__(self,parent=None):
super(TcpThread2,self).__init__(parent)
self.thread = QThread()
self.worker = Work2()
# self.worker.count_signal.connect(self.flush)
self.worker.signalDataReceive.connect(self.flush_str)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.tcpWork)
self.thread.start()
# self.thread.started.connect(self.worker.work)
self.thread.finished.connect(self.finished)
def flush_str(self,msg):
msg = str(msg)
# self.dataChanged.emit()
print(msg)
pass
# self.label.setText(str(msg))
def flush(self, count):
pass
# self.label.setText(str(count))
def workStart(self):
print('button start.')
# self.pushButton_Start.setEnabled(False)
self.thread.start()
def workStop(self):
print('button stop.')
self.worker.work_stop()
self.thread.quit()
def finished(self):
print('finish.')
# self.pushButton_Start.setEnabled(True)
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__(parent)
self.setupUi(self)
self.pushButton_Start.clicked.connect(self.workStart)
self.pushButton_Stop.clicked.connect(self.workStop)
self.thread = QThread()
self.worker = Work2()
# self.worker.count_signal.connect(self.flush)
self.worker.signalDataReceive.connect(self.flush_str)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.tcpWork)
self.thread.start()
# self.thread.started.connect(self.worker.work)
self.thread.finished.connect(self.finished)
def flush_str(self,msg):
self.label.setText(str(msg))
def flush(self, count):
self.label.setText(str(count))
def workStart(self):
print('button start.')
self.pushButton_Start.setEnabled(False)
self.thread.start()
def workStop(self):
print('button stop.')
self.worker.work_stop()
self.thread.quit()
def finished(self):
print('finish.')
self.pushButton_Start.setEnabled(True)
def main_qml_thred_PyQt5_view():
path = "test.qml"
app = QGuiApplication([])
view = QQuickView()
con = MyClass()
context = view.rootContext()
context.setContextProperty("con",con)
view.engine().quit.connect(app.quit)
view.setSource(QUrl(path))
view.show()
app.exec_()
def main_qml_thread():
app = QGuiApplication(sys.argv)
qmlRegisterType(TcpThread2,"TcpMoveToThread",1,0,"TcpMoveToThread")
engine = QQmlApplicationEngine()
app.setOrganizationName("Some Company")
app.setOrganizationDomain("somecompany.com")
app.setApplicationName("Amazing Application")
url = QUrl("./ui_qml/PyQt5_thread2.qml")
engine.load(url)
app.exec_()
def main_qml_thread_PyQt5_app():
path = QUrl("./ui_qml/pyqt5_thread2.qml")
app = QGuiApplication([])
# qmlRegisterType(PieChart, "Charts", 1, 0, "PieChart")
qmlRegisterType(TcpThread2,"TcpMoveToThread",1,0,"TcpMoveToThread")
engine = QQmlApplicationEngine()
engine.load(path)
app.exec_()
if __name__ == "__main__":
print('sys.argv',sys.argv)
main_qml_thread_PyQt5_app()
#moveToThread_main.py
# -*- coding: utf-8 -*-
from PySide2.QtQml import qmlRegisterType,QQmlApplicationEngine
from PySide2.QtQuick import QQuickView
from PySide2.QtGui import QGuiApplication
from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import Signal,Slot,QObject, QThread,QUrl,QSettings
from PySide2.QtNetwork import QTcpSocket
from QtUi import Ui_MainWindow
import sys
import time
class Work2(QObject):
count = int(0)
count_signal = Signal(int)
signalDataReceive = Signal(str)
def __init__(self):
super().__init__()
self.m_socket = QTcpSocket()
self.run = True
@Slot()
def tcpWork(self):
print('hello tcpwork')
self.m_socket = QTcpSocket()
self.m_socket.connectToHost("127.0.0.1",8000)
if self.m_socket.waitForConnected(-1):
while True:
# tm = self.m_socket.waitForReadyRead()
# print('tm',tm)
# tm = self.m_socket.waitForReadyRead()
# print('tm2',tm)
if self.m_socket.waitForReadyRead():
# tm = False
res = self.m_socket.readAll()
self.msg = str(res.data())
# if self.msg != b'':
print('recv:',self.msg)
# time.sleep(0.001)#1ms
self.signalDataReceive.emit(self.msg)
def work(self):
self.run = True
while self.run:
print(str(self.count))
self.count += 1
self.count_signal.emit(self.count)
time.sleep(1)
def work_stop(self):
self.run = False
class Work(QObject):
count = int(0)
count_signal = Signal(int)
def __init__(self):
super(Work, self).__init__()
self.run = True
def work(self):
self.run = True
while self.run:
print(str(self.count))
self.count += 1
self.count_signal.emit(self.count)
time.sleep(1)
def work_stop(self):
self.run = False
class TcpThread2(QObject):
dataChanged = Signal()
def __init__(self):
super().__init__()
self.thread = QThread()
self.worker = Work2()
# self.worker.count_signal.connect(self.flush)
self.worker.signalDataReceive.connect(self.flush_str)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.tcpWork)
self.thread.start()
# self.thread.started.connect(self.worker.work)
self.thread.finished.connect(self.finished)
def flush_str(self,msg):
msg = str(msg)
# self.dataChanged.emit()
print(msg)
pass
# self.label.setText(str(msg))
def flush(self, count):
pass
# self.label.setText(str(count))
def workStart(self):
print('button start.')
# self.pushButton_Start.setEnabled(False)
self.thread.start()
def workStop(self):
print('button stop.')
self.worker.work_stop()
self.thread.quit()
def finished(self):
print('finish.')
# self.pushButton_Start.setEnabled(True)
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)
self.pushButton_Start.clicked.connect(self.workStart)
self.pushButton_Stop.clicked.connect(self.workStop)
self.thread = QThread()
self.worker = Work2()
# self.worker.count_signal.connect(self.flush)
self.worker.signalDataReceive.connect(self.flush_str)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.tcpWork)
self.thread.start()
# self.thread.started.connect(self.worker.work)
self.thread.finished.connect(self.finished)
def flush_str(self,msg):
self.label.setText(str(msg))
def flush(self, count):
self.label.setText(str(count))
def workStart(self):
print('button start.')
self.pushButton_Start.setEnabled(False)
self.thread.start()
def workStop(self):
print('button stop.')
self.worker.work_stop()
self.thread.quit()
def finished(self):
print('finish.')
self.pushButton_Start.setEnabled(True)
def main_qml_thred_PySide2_view():
path = "test.qml"
app = QGuiApplication([])
view = QQuickView()
con = MyClass()
context = view.rootContext()
context.setContextProperty("con",con)
view.engine().quit.connect(app.quit)
view.setSource(QUrl(path))
view.show()
app.exec_()
def main_qml_thread():
app = QGuiApplication(sys.argv)
qmlRegisterType(TcpThread2,"TcpMoveToThread",1,0,"TcpMoveToThread")
engine = QQmlApplicationEngine()
app.setOrganizationName("Some Company")
app.setOrganizationDomain("somecompany.com")
app.setApplicationName("Amazing Application")
url = QUrl("./ui_qml/PySide2_thread2.qml")
engine.load(url)
app.exec_()
def main_qml_thread_PySide2_app():
path = QUrl("./ui_qml/pyqt5_thread2.qml")
app = QGuiApplication([])
qmlRegisterType(TcpThread2,"TcpMoveToThread",1,0,"TcpMoveToThread")
engine = QQmlApplicationEngine()
engine.load(path)
app.exec_()
if __name__ == "__main__":
print('sys.argv',sys.argv)
main_qml_thread_PySide2_app()
// import QtQuick 2.12
//import QtQuick.Window 2.12
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.12
import TcpMoveToThread 1.0
// import Charts 1.0
ApplicationWindow {
visible: true
height: 300
width: 400
// PieChart {
// id: aPieChart
// anchors.centerIn: parent
// width: 100; height: 100
// color: "red"
// onChartCleared: console.log("The chart has been cleared")
// }
Button{
id: redbutton
anchors.left: parent.left
anchors.top: parent.top
text: "加载红色"
onClicked: {
recloader.sourceComponent=redRec;
}
}
Button{
id: bluebutton
anchors.right: parent.right
anchors.top: parent.top
text: "加载蓝色"
onClicked: {
recloader.sourceComponent=blueRec;
// tcp.dataChangedSlot('kytty33')
}
}
Text {
id: message
text: qsTr("text")
font.pixelSize: 25
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: recloader.bottom
}
// Connections{
// target: tcp
// function onDataChanged() {
// message.text= 'htpps'//tcp.M_data //tcp.getDate; //此处连接了TcpMoveToThread类的信号,一旦数据改变,就改变message的内容
// console.log('message.text'+message.text+'#')
// }
// }
TcpMoveToThread{
id: tcp
}
onClosing: function(closeevent){
//CloseEvent的accepted设置为false就能忽略该事件
// closeevent.accepted = false
}
Loader{
id: recloader
anchors.centerIn: parent
height: 100
width: 100
}
Component{
id: redRec
Rectangle{
color: "red"
}
}
Component{
id:blueRec
Rectangle{
color: "blue"
}
}
}