我正在编写一个PySide2应用程序,将结果绘制到特定的计算中,并尝试对计算进行多线程处理以避免锁定GUI。我正在尝试使用QThreadPool与与绘图相关的选项进行交互时,在一个单独的线程中运行计算,该线程通过信号将结果返回给回调方法,该回调方法使用matplotlib绘制结果。
问题在于,当我连续(而不是不合理地快速)更改选项的选择时,应用崩溃。如果删除线程,则不会发生这种情况。
我知道很多问题是由于绘图发生在工作线程而不是主线程中,所以我相信我已经确保绘图仅发生在主线程中。
我想部分问题是,使用信号和插槽时,我可能会误解正在运行的地方。我尝试查找在代码的不同点使用了什么线程,但是只能使用QThread.currentThread(),它返回地址,并且对QThread.currentThreadId()导致此错误没有太大帮助:AttributeError:类型对象“ PySide2.QtCore.QThread”没有属性“ currentThreadId”。
我试图通过编写一个类似崩溃的最小版本的应用程序来隔离行为,其中大部分已在下面进行了介绍。我已经排除了计算方法,因为我不确定是否可以共享该计算方法,而我已用带有一些选项的QListWidget替换了绘图选项。与正常的应用相比,崩溃需要更多的交互,而在某些情况下,仅在一两秒钟之内只选择了几个选项,崩溃的正常应用还是很有希望。
class MainWindow(QObject):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.main_window = QMainWindow()
self.main_window.setCentralWidget(QWidget())
self.main_window.centralWidget().setLayout(QHBoxLayout())
self.setup_main_window()
def setup_main_window(self):
print(f'setup_main_window thread address: {QThread.currentThreadId()}')
self.load_list()
self.plot_figure = PlotFigure()
self.canvas = FigureCanvas(self.plot_figure)
self.plot_figure.plot(update=False)
self.main_window.centralWidget().layout().addWidget(self.canvas)
def load_list(self):
self.order_list = QListWidget(self.main_window)
self.list_items = [
QListWidgetItem('1', self.order_list),
QListWidgetItem('2', self.order_list),
QListWidgetItem('3', self.order_list),
QListWidgetItem('4', self.order_list),
]
self.order_list.itemClicked.connect(self.order_list_item_changed)
self.main_window.centralWidget().layout().addWidget(self.order_list)
def order_list_item_changed(self):
print(f'order_list_item_changed thread address: {QThread.currentThreadId()}')
self.plot_figure.plot()
def show(self):
if self.main_window is not None:
self.main_window.show()
class PlotFigure(Figure):
def __init__(self):
super().__init__()
def plot(self):
print(f'plot thread address: {QThread.currentThreadId()}')
print(f'update: {update}')
print(f'connecting signals')
worker = Worker(self.calc)
#worker.signals.close.connect(self.set_end_calc)
worker.signals.finished.connect(self.plot_result)
print(f'threads: {QThreadPool.globalInstance().activeThreadCount()}')
QThreadPool.globalInstance().start(worker)
def plot_result(self, m, xs, ys):
print(f'plot_result thread address: {QThread.currentThreadId()}')
print('plotting')
fig = self.canvas.figure
fig.clear()
self.axis = fig.add_subplot(111)
self.image = self.axis.imshow(m,
origin='lower',
aspect='auto',
cmap=matplotlib.cm.get_cmap('inferno'),
interpolation='bilinear',
extent=(xs[0], xs[-1], ys[0], ys[-1])
)
self.canvas.draw()
class WorkerSignals(QtCore.QObject):
close = QtCore.Signal(bool)
start = QtCore.Signal(bool)
finished = QtCore.Signal(list, list, list)
class Worker(QtCore.QRunnable):
def __init__(self, worker_method):
super(Worker, self).__init__()
self.signals = WorkerSignals()
self.worker_method = worker_method
def run(self):
self.signals.close.emit(True)
print('close signal sent')
m, xs, ys = self.worker_method()
print('calc done')
self.signals.finished.emit(m, xs, ys)
我应该能够选择新选项(在列表小部件周围单击),从线程池中启动一个新线程,该线程将运行计算并发回要绘制的结果。如果在短时间内选择了太多选项,则应用程序将崩溃。当所有事情都发生在主线程中时,这不会发生。
有人可以告诉我为什么该应用可能崩溃,并提供解决崩溃的解决方案吗?
答案 0 :(得分:0)
您所描述的崩溃行为指向试图同时对同一数据/变量进行操作的多个线程。由于您没有限制可以启动的其他线程的数量,因此如果用户快速连续触发多个工作程序,则很有可能会发生这种情况。
有几种处理方法,具体取决于您的要求。
如果由于两个线程正在访问相同的内存而发生崩溃,则简单的解决方案是获取数据的副本。请注意,对于列表,字典等,您将需要deepcopy
数据以确保嵌套值也被复制。对于大数据,这可能会导致一些开销。
您没有在示例中包含calc
方法,因此我无法建议您如何进行此操作,但是一般而言。
from copy import copy
data = 'a simple string'
data_c = copy(data)
或者,深拷贝
from copy import deepcopy
data = {'a':'dict', 'of':'items'}
data_c = deepcopy(data)
如果您有一个特定的工作程序,一次只能运行一个工作程序,则可以实现原始锁。下面假设您希望 latest 工作程序始终运行,并且在新任务到达时将丢弃所有较早的工作程序。
self._worker_lock = False
self._worker_waiting = False
每个工作人员.finished
信号都应连接到执行的方法。
def worker_finised(self):
self._worker_lock = False
if self._worker_waiting:
QThreadPool.globalInstance().start(self._worker_waiting)
self._worker_waiting = False
当前工作完成后,它将自动启动下一个工作程序。如果希望所有发生的工作程序都运行,则可以使用list
队列。
最后,当启动工作程序时。
def plot()
worker = Worker(self.calc)
worker.signals.finished.connect(self.plot_result)
if self._worker_lock:
self._worker_waiting = worker
else:
self._worker_lock
QThreadPool.globalInstance().start(worker)
这是一种愚蠢的方法,它还会阻止您将线程用于其他计算,并使线程池变得无用。
threadpool = QThreadPool()
threadpool. setMaxThreadCount(1)
即使这可能还不够,如果draw和calc方法仍然可以同时执行,那么仍然会崩溃。但是,如果您的线程仅相互冲突,那就足够了。