PyQt5线程排序

PyQt5 thread sequencing

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

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

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

我希望在第一行执行反向操作,然后在同一行执行撤消操作,然后再移动到 table 中的下一行,更新 [=] 中的更改36=]

这是结果视图 table:

代码如下:

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

这是我希望在每一行之后更新 table 时得到的结果:

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

感谢您的宝贵时间。

您的代码存在以下问题:

  • 您不应该在执行 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