使用 concurrent.futures.ThreadPoolExecutor() 时 PyQt5 小部件 Qthread 问题
PyQt5 widget Qthread issue when using concurrent.futures.ThreadPoolExecutor()
我一直在尝试使用 concurrent.futures.ThreadPoolExecutor()
来 运行 我的应用程序中的一些后台任务,这样我就可以在执行这些任务(“测量”)时与 GUI 进行交互 运行。这些任务完成后,我分配一个回调函数来更新 GUI 的某些字段,然后尝试根据这些字段更新 GUI 小部件(绘图、表格、列表等)。
这是一个例子:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
*some more code goes here*
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
def perform_measurement():
future = self.executor.submit(*a function*)
future.add_done_callback(self.update_gui_fields)
def update_gui_fields(self, future):
data = future.result()
self.items_for_list.append(QStandardItem(data['key']))
*more fields are updated here*
self.QListView1.setModel(self.items_for_list)
*more widgets are updated here*
问题是字段更新正常,但是当我尝试与小部件交互时,应用程序崩溃了。这是因为子级(此处为 self.items_for_list
)与父级(此处为 self.QListView1
)处于不同的线程中。这是我得到的错误:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QListView(0x555795efbc10), parent's thread is QThread(0x555795296600), current thread is QThread(0x7fd12400a100)
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
我在以前的帖子中找不到任何解决方案。知道如何攻击这个吗?
谢谢!
与 add_done_callback 关联的回调在辅助线程中执行,根据您的代码,您正尝试从该辅助线程更新 GUI,这是被禁止的,因此 Qt 会抛出该警告。解决方案是通过创建一个通过信号转发该信息的 QObject 来实现逻辑:
import concurrent.futures
import sys
import time
from PyQt5 import QtCore, QtGui, QtWidgets
def measure():
time.sleep(5)
return {"key": "value"}
class TaskManager(QtCore.QObject):
finished = QtCore.pyqtSignal(object)
def __init__(self, parent=None, max_workers=None):
super().__init__(parent)
self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)
@property
def executor(self):
return self._executor
def submit(self, fn, *args, **kwargs):
future = self.executor.submit(fn, *args, **kwargs)
future.add_done_callback(self._internal_done_callback)
def _internal_done_callback(self, future):
data = future.result()
self.finished.emit(data)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.model = QtGui.QStandardItemModel()
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
self.button = QtWidgets.QPushButton("launch")
self._manager = TaskManager(max_workers=1)
self._manager.finished.connect(self.update_gui_fields)
self.button.clicked.connect(self.perform_measurement)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(self.view)
lay.addWidget(self.button)
def perform_measurement(self):
self._manager.submit(measure)
def update_gui_fields(self, data):
self.model.appendRow(QtGui.QStandardItem(data["key"]))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
我一直在尝试使用 concurrent.futures.ThreadPoolExecutor()
来 运行 我的应用程序中的一些后台任务,这样我就可以在执行这些任务(“测量”)时与 GUI 进行交互 运行。这些任务完成后,我分配一个回调函数来更新 GUI 的某些字段,然后尝试根据这些字段更新 GUI 小部件(绘图、表格、列表等)。
这是一个例子:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
*some more code goes here*
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
def perform_measurement():
future = self.executor.submit(*a function*)
future.add_done_callback(self.update_gui_fields)
def update_gui_fields(self, future):
data = future.result()
self.items_for_list.append(QStandardItem(data['key']))
*more fields are updated here*
self.QListView1.setModel(self.items_for_list)
*more widgets are updated here*
问题是字段更新正常,但是当我尝试与小部件交互时,应用程序崩溃了。这是因为子级(此处为 self.items_for_list
)与父级(此处为 self.QListView1
)处于不同的线程中。这是我得到的错误:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QListView(0x555795efbc10), parent's thread is QThread(0x555795296600), current thread is QThread(0x7fd12400a100)
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
我在以前的帖子中找不到任何解决方案。知道如何攻击这个吗? 谢谢!
与 add_done_callback 关联的回调在辅助线程中执行,根据您的代码,您正尝试从该辅助线程更新 GUI,这是被禁止的,因此 Qt 会抛出该警告。解决方案是通过创建一个通过信号转发该信息的 QObject 来实现逻辑:
import concurrent.futures
import sys
import time
from PyQt5 import QtCore, QtGui, QtWidgets
def measure():
time.sleep(5)
return {"key": "value"}
class TaskManager(QtCore.QObject):
finished = QtCore.pyqtSignal(object)
def __init__(self, parent=None, max_workers=None):
super().__init__(parent)
self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)
@property
def executor(self):
return self._executor
def submit(self, fn, *args, **kwargs):
future = self.executor.submit(fn, *args, **kwargs)
future.add_done_callback(self._internal_done_callback)
def _internal_done_callback(self, future):
data = future.result()
self.finished.emit(data)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.model = QtGui.QStandardItemModel()
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
self.button = QtWidgets.QPushButton("launch")
self._manager = TaskManager(max_workers=1)
self._manager.finished.connect(self.update_gui_fields)
self.button.clicked.connect(self.perform_measurement)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(self.view)
lay.addWidget(self.button)
def perform_measurement(self):
self._manager.submit(measure)
def update_gui_fields(self, data):
self.model.appendRow(QtGui.QStandardItem(data["key"]))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())