簡體   English   中英

PyQt5 線程排序

[英]PyQt5 thread sequencing

我很難按順序使用 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_()

這是我目前得到的印刷 output:

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

謝謝你的時間。

您的代碼存在以下問題:

  • 您不應該在執行 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_()

Output:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM