【问题标题】:PyQt5 thread sequencingPyQt5 线程排序
【发布时间】:2021-07-29 17:40:39
【问题描述】:

我在尝试按顺序使用 PyQt 和 QThreads 遍历表中的每一行时遇到了困难。我已经简化了下面的代码(我的原始代码涉及执行 SQL 查询的线程)

下面的代码创建了一个按钮和一个三行三列的表格。按下启动按钮时,将从表中的第一行读取文本并将其传递给线程。在线程中(具有模拟延迟),字符串被反转并传递回主线程。接下来,将反转的字符串传递回另一个线程实例,并将字符串返回到原始顺序并更新表。计划是遍历每一行

我的问题是我的代码似乎首先对所有行执行所有反向字符串操作,然后对所有行执行所有字符串撤消操作,然后将更新转储到最后的表中

我希望对第一行执行相反的操作,然后在同一行执行撤消操作,然后再转到表中的下一行,同时更新表中的更改

这是结果表的视图:

代码如下:

import sys
import time
from datetime import datetime
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QPushButton, QHBoxLayout
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5 import QtGui
import functools


class WorkerSignals(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(str)
    result = pyqtSignal(object)


class Worker(QObject):
    def __init__(self, string_list):
        super(Worker, self).__init__()
        self.signals = WorkerSignals()  # Create an instance of our signals class.
        self.string_list = string_list

    def run(self):
        for string in self.string_list:
            time.sleep(1)
            self.signals.result.emit(string[::-1])
        self.signals.finished.emit()


class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle('Testing ')
        central = QWidget(self)
        self.setCentralWidget(central)
        mainLayout = QHBoxLayout(central)
        buttonLayout = QVBoxLayout()
        mainLayout.addLayout(buttonLayout)

        button1 = QPushButton('Launch') # Add launch button
        buttonLayout.addWidget(button1)

        tableLayout = QVBoxLayout()
        mainLayout.addLayout(tableLayout)

        self.tableWidget = QTableWidget(self.centralWidget()) # Add table
        self.tableWidget.setRowCount(3)
        self.tableWidget.setColumnCount(3)
        self.tableWidget.setItem(0, 0, QTableWidgetItem("Test A"))
        self.tableWidget.setItem(0, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(0, 2, QTableWidgetItem(""))
        self.tableWidget.setItem(1, 0, QTableWidgetItem("Test B"))
        self.tableWidget.setItem(1, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(1, 2, QTableWidgetItem(""))
        self.tableWidget.setItem(2, 0, QTableWidgetItem("Test C"))
        self.tableWidget.setItem(2, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(2, 2, QTableWidgetItem(""))

        tableLayout.addWidget(self.tableWidget)
        self.show()

        button1.clicked.connect(self.start_tests) # Trigger start_tests method on button clicked

    def call_worker(self, my_string, fn, row):
        """ Prep worker thread request in main thread"""
        self.my_string = my_string
        self.fn = fn
        self.row = row

        self.thread = QThread(self)
        self.worker = Worker([self.my_string])
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)

        self.worker.signals.result.connect(functools.partial(self.fn, self.row))
        self.worker.signals.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        self.thread.start()
        self.thread.quit()
        self.thread.wait()
        return self.thread

    def start_tests(self):
        self.launch_time = datetime.now().strftime('%Y-%m-%d__%H-%M-%S')
        print(self.launch_time)

        all_rows = self.tableWidget.rowCount()
        all_rows = [i for i in range(0, all_rows)]

        """Iterate through each row in table to perform a couple of actions, by calling a thread to run first to 
        reverse a string, then to pass to another method to undo the reverse in another thread, update the table row
        then proceed to the next row in the table"""
        for row in all_rows:
            self.tableWidget.setItem(row, 1, QTableWidgetItem('Queued'))
            self.tableWidget.item(row, 1).setBackground(QtGui.QColor.fromRgb(255, 255, 0))

            self.test_str = (self.tableWidget.item(row, 0)).text()

            # First thread call
            self.call_worker(self.test_str, self.undo_changes, row)

    def undo_changes(self, row, reverse_str):
        self.row = row
        self.reverse_str = reverse_str

        print(f'String reversed to: {self.reverse_str}')

        self.tableWidget.setItem(self.row, 1, QTableWidgetItem('Passed'))
        self.tableWidget.item(self.row, 1).setBackground(QtGui.QColor.fromRgb(0, 255, 255))

        # Second thread call
        self.call_worker(self.reverse_str, self.add_to_third_column, self.row)


    def add_to_third_column(self, row, orig_string):
        self.row = row
        self.orig_string = orig_string

        print(f'String RETURNED to: {self.orig_string}')
        # update table row
        self.tableWidget.setItem(self.row, 2, QTableWidgetItem(self.orig_string))
        self.tableWidget.item(self.row, 2).setBackground(QtGui.QColor.fromRgb(255, 0, 255))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    app.exec_()

这是我目前得到的打印输出:

String reversed to: A tseT
String reversed to: B tseT
String reversed to: C tseT
String RETURNED to: Test A
String RETURNED to: Test B
String RETURNED to: Test C

这是我在每行之后更新表格时希望得到的:

String reversed to: A tseT    
String RETURNED to: Test A    #Then update table
String reversed to: B tseT
String RETURNED to: Test B    #Then update table
String reversed to: C tseT   
String RETURNED to: Test C    #Then update table

感谢您的宝贵时间。

【问题讨论】:

  • 请注意,Worker 已经继承自 QObject,不需要单独的信号对象。此外,如果您在线程上调用 wait,您实际上是在使线程的点无效,因为该函数在线程完成之前不会返回(因此是您的结果)。

标签: python multithreading pyqt pyqt5


【解决方案1】:

您的代码存在以下问题:

  • 您不应该在执行 start() 后立即使用 quit(),因为该方法会阻塞事件循环,直到线程结束,而这在 GUI 中是不应该的,因为它会冻结。

  • 不要滥用属性,在真正需要的时候使用self.foo,并不是所有东西都必须是属性。

  • 没有必要创建 WorkerSignals,因为 Worker 是一个可以有信号的 QObject,这种技术用于不能有自己的信号的 QRunnables。

  • 如果您希望线程在队列中运行,那么您必须实现当一个任务完成另一个任务启动时,for 循环在这种情况下不起作用的逻辑。在 Qt 中,您必须使用事件来执行操作。

综合以上情况,解决办法是:

import sys
import time
from functools import partial

from datetime import datetime

from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
    QMainWindow,
    QApplication,
    QWidget,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QPushButton,
    QHBoxLayout,
)


class Worker(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(str)
    result = pyqtSignal(object)

    def __init__(self, string_list):
        super(Worker, self).__init__()
        self.string_list = string_list

    def run(self):
        for string in self.string_list:
            time.sleep(1)
            self.result.emit(string[::-1])
        self.finished.emit()


class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("Testing ")
        central = QWidget(self)
        self.setCentralWidget(central)
        mainLayout = QHBoxLayout(central)
        buttonLayout = QVBoxLayout()
        mainLayout.addLayout(buttonLayout)

        button1 = QPushButton("Launch")  # Add launch button
        buttonLayout.addWidget(button1)

        tableLayout = QVBoxLayout()
        mainLayout.addLayout(tableLayout)

        self.tableWidget = QTableWidget(self.centralWidget())  # Add table
        self.tableWidget.setRowCount(3)
        self.tableWidget.setColumnCount(3)
        self.tableWidget.setItem(0, 0, QTableWidgetItem("Test A"))
        self.tableWidget.setItem(0, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(0, 2, QTableWidgetItem(""))
        self.tableWidget.setItem(1, 0, QTableWidgetItem("Test B"))
        self.tableWidget.setItem(1, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(1, 2, QTableWidgetItem(""))
        self.tableWidget.setItem(2, 0, QTableWidgetItem("Test C"))
        self.tableWidget.setItem(2, 1, QTableWidgetItem(""))
        self.tableWidget.setItem(2, 2, QTableWidgetItem(""))

        tableLayout.addWidget(self.tableWidget)

        button1.clicked.connect(self.start_tests)

    def call_worker(self, my_string, fn, row):
        """Prep worker thread request in main thread"""

        thread = QThread(self)
        worker = Worker([my_string])
        worker.moveToThread(thread)
        thread.worker = worker

        thread.started.connect(worker.run)
        worker.result.connect(partial(fn, row))
        worker.finished.connect(worker.deleteLater)
        worker.finished.connect(thread.quit)
        thread.finished.connect(thread.deleteLater)

        thread.start()

    def start_tests(self):
        launch_time = datetime.now().strftime("%Y-%m-%d__%H-%M-%S")
        print(launch_time)

        all_rows = self.tableWidget.rowCount()
        all_rows = [i for i in range(0, all_rows)]

        for row in all_rows:
            item = QTableWidgetItem("Queued")
            item.setBackground(QColor.fromRgb(255, 255, 0))
            self.tableWidget.setItem(row, 1, item)

        self.start_reversed(0)

    def start_reversed(self, row):
        test_str = self.tableWidget.item(row, 0).text()
        self.call_worker(test_str, self.start_undo, row)

    def start_undo(self, row, reverse_str):
        print(f"String reversed to: {reverse_str}")

        item1 = self.tableWidget.item(row, 1)
        item1.setText("Passed")
        item1.setBackground(QColor.fromRgb(0, 255, 255))
        self.call_worker(reverse_str, self.finished_undo, row)

    def finished_undo(self, row, orig_string):
        item0 = self.tableWidget.item(row, 0)
        item0.setText(orig_string)
        print(f"String RETURNED to: {orig_string}")

        item = QTableWidgetItem(orig_string)
        item.setBackground(QColor.fromRgb(255, 0, 255))
        self.tableWidget.setItem(row, 2, item)

        row += 1
        if row < self.tableWidget.rowCount():
            self.start_reversed(row)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

输出:

String reversed to: A tseT
String RETURNED to: Test A
String reversed to: B tseT
String RETURNED to: Test B
String reversed to: C tseT
String RETURNED to: Test C

【讨论】:

  • 非常感谢@eyllanesc,正如您所说,它工作正常。 QRunnables 信号是我尝试 QRunnables 技术时遗留下来的,我切换到 QThread 看看这是否解决了我最初的问题。出于兴趣,如果我切换回 QRunnables 技术并恢复工作信号,我能否得到相同的行为?我猜关键是在方法之间传递“行”号
猜你喜欢
  • 1970-01-01
  • 2017-12-08
  • 2015-08-21
  • 2017-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-27
相关资源
最近更新 更多