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
我在尝试按顺序使用 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