对于我基于Python和Qt的项目,我想将昂贵的计算和提供服务器/客户机功能的函数转移到单独的线程中,以解冻我的GUI.在让它们运行的同时,我仍然希望它们定期判断是否有来自主线程的新数据.为了进行测试,我因此实现了以下演示代码:

import sys
from time import sleep
import shiboken6

from PySide6.QtCore import Qt, QObject, QThread, Signal, Slot, QTimer
from PySide6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Worker(QObject):
    finished = Signal()
    progress = Signal(int)

    def __init__(self):
        super().__init__()
        self.print_to_console_plz = False

    @Slot()
    def print_on_console_while_running(self):
        self.print_to_console_plz = True
        print("Set print_to_console to true")

    def run(self):
        timer = QTimer()
        for i in range(5):
            sleep(0.9)
            timer.start(100)
            if self.print_to_console_plz:
                print("Hello World from worker")
                self.print_to_console_plz = False
            self.progress.emit(i + 1)
        self.finished.emit()

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.clicksCount = 0
        self.initWorker()
        self.setupUi()

    def initWorker(self):
        self.thread = QThread()
        # Step 3: Create a worker object
        self.worker = Worker()
        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)
        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.reportProgress)

    def setupUi(self):
        self.setWindowTitle("Freezing GUI")
        self.resize(300, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        # Create and connect widgets
        self.clicksLabel = QLabel("Counting: 0 clicks", self)
        self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.stepLabel = QLabel("Long-Running Step: 0")
        self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.clicksToConsoleLabel = QLabel("Click here to print to console", self)
        self.clicksToConsoleLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.countBtn = QPushButton("Click me!", self)
        self.countBtn.clicked.connect(self.countClicks)
        self.ToConsoleBttn = QPushButton("Print to console!", self)
        self.ToConsoleBttn.clicked.connect(self.worker.print_on_console_while_running)
        self.longRunningBtn = QPushButton("Long-Running Task!", self)
        self.longRunningBtn.clicked.connect(self.runLongTask)
        # Set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.clicksLabel)
        layout.addWidget(self.countBtn)
        layout.addStretch()
        layout.addWidget(self.clicksToConsoleLabel)
        layout.addWidget(self.ToConsoleBttn)
        layout.addStretch()
        layout.addWidget(self.stepLabel)
        layout.addWidget(self.longRunningBtn)
        self.centralWidget.setLayout(layout)

    def countClicks(self):
        self.clicksCount += 1
        self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")

    def reportProgress(self, n):
        self.stepLabel.setText(f"Long-Running Step: {n}")

    def runLongTask(self):
        """Long-running task in 5 steps."""
        # Step 6: Start the thread
        if not shiboken6.isValid(self.thread):
            self.initWorker()
            self.ToConsoleBttn.clicked.connect(self.worker.print_on_console_while_running)
        self.thread.start()

        # Final resets
        self.longRunningBtn.setEnabled(False)
        self.thread.finished.connect(
            lambda: self.longRunningBtn.setEnabled(True)
        )
        self.thread.finished.connect(
            lambda: self.stepLabel.setText("Long-Running Step: 0")
        )

app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

我的主要目的是让"昂贵"的函数在计数时在工作线程中运行,但它仍然应该定期判断是否有新数据可用(通过调用print_on_console_while_running表示).为了避免run函数在执行时阻塞所有内容,我还引入了QTimer作为非阻塞计时器.

无论如何,每当我按下"打印到控制台"按钮时当worker执行run函数时,我总是在run函数完成后打印"Set print\u to\u console to true",而不是在执行过程中,这表明run函数仍然会阻止其他所有操作的执行.

我在这里做错了什么,如何在仍然执行run函数的情况下将数据从主线程发送到工作线程?

推荐答案

问题是由于插槽位于接收器线程中,因此Qt自动使用QueuedConnection:

当控制返回到接收器线程的事件循环时,将调用插槽.插槽在接收器的线程中执行.

由于线程被run()的执行占用,因此只有在run()返回时才会调用print_on_console_while_running.

一种可能的解决方案是force A直接连接:

当信号发出时,立即调用插槽.插槽在信令线程中执行.

    self.ToConsoleBttn.clicked.connect(
        self.worker.print_on_console_while_running, Qt.DirectConnection)

通过这种方式,立即调用插槽并立即设置变量.

另一种常见的方法(只要线程不需要实际的事件循环)是直接将QThread子类化,并只覆盖其run().

由于QThread是线程的handler(不需要moveToThread),因此对其任何函数/插槽的任何连接都将在创建它的同一个线程中(因此,通常是主线程),只有run()将在单独的线程中执行,这意味着在该QThread子类中实现print_on_console_while_running将始终自动使用直接连接.

请注意,如果要在线程完成后再次启动线程,则不需要删除并重新创建它.还要注意,您正在创建的QTimer完全没有用处,这不仅是因为它超时时不做任何事情,而且主要是因为time.sleep会阻止其处理.最后,对于线程连接,通常最好避免lambda,尤其是当对象将被 destruct 时

Python相关问答推荐

ambda将时间戳与组内另一列的所有时间戳进行比较

从管道将Python应用程序部署到Azure Web应用程序,不包括需求包

将HLS纳入媒体包

仅从风格中获取 colored颜色 循环

如何标记Spacy中不包含特定符号的单词?

. str.替换pandas.series的方法未按预期工作

对于一个给定的数字,找出一个整数的最小和最大可能的和

可变参数数量的重载类型(args或kwargs)

Python,Fitting into a System of Equations

如何获得每个组的时间戳差异?

ThreadPoolExecutor和单个线程的超时

我想一列Panadas的Rashrame,这是一个URL,我保存为CSV,可以直接点击

部分视图的DataFrame

在www.example.com中使用`package_data`包含不包含__init__. py的非Python文件

lityter不让我输入左边的方括号,'

Matplotlib中的字体权重

PYTHON、VLC、RTSP.屏幕截图不起作用

将CSS链接到HTML文件的问题

如何获得3D点的平移和旋转,给定的点已经旋转?

仅取消堆叠最后三列