我在使用文件保存操作时冻结的GUI时遇到了一些麻烦,这需要一段时间,我很想知道为什么会这样。
我按照Schollii's wonderful answer上的similar question的说明操作,但必须有一些我不知道的东西,因为我无法按照我的预期运行GUI。
以下示例不可运行,因为它只显示相关部分,但希望它足以让讨论继续进行。基本上我有一个生成一些大数据的主应用程序类,我需要将它保存为HDF5格式,但这需要一些时间。为了使GUI响应,主类创建Saver
类的对象和QThread
以进行实际数据保存(使用moveToThread
)。
此代码的输出几乎是我所期望的(即我看到一条消息“保存线程”具有与“主”线程不同的线程ID)所以我知道正在创建另一个线程。数据也已成功保存,因此该部件正常工作。
在实际数据保存期间(可能需要几分钟),GUI冻结并在Windows上“无响应”。关于出了什么问题的任何线索?
跑步期间的标准输出:
outer thread "main" (#15108)
<__main__.Saver object at 0x0000027BEEFF3678> running SaveThread
Saving data from thread "saving_thread" (#13624)
代码示例:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot, QObject
class MyApp(QtWidgets.QMainWindow, MyAppDesign.Ui_MainWindow):
def save_file(self):
self.save_name, _ = QtWidgets.\
QFileDialog.getSaveFileName(self)
QThread.currentThread().setObjectName('main')
outer_thread_name = QThread.currentThread().objectName()
outer_thread_id = int(QThread.currentThreadId())
# print debug info about main app thread:
print('outer thread "{}" (#{})'.format(outer_thread_name,
outer_thread_id))
# Create worker and thread to save the data
self.saver = Saver(self.data,
self.save_name,
self.compressionSlider.value())
self.save_thread = QThread()
self.save_thread.setObjectName('saving_thread')
self.saver.moveToThread(self.save_thread)
# Connect signals
self.saver.sig_done.connect(self.on_saver_done)
self.saver.sig_msg.connect(print)
self.save_thread.started.connect(self.saver.save_data)
self.save_thread.start())
@pyqtSlot(str)
def on_saver_done(self, filename):
print('Finished saving {}'.format(filename))
''' End Class '''
class Saver(QObject):
sig_done = pyqtSignal(str) # worker id: emitted at end of work()
sig_msg = pyqtSignal(str) # message to be shown to user
def __init__(self, data_to_save, filename, compression_level):
super().__init__()
self.data = data_to_save
self.filename = filename
self.compression_level = compression_level
@pyqtSlot()
def save_data(self):
thread_name = QThread.currentThread().objectName()
thread_id = int(QThread.currentThreadId())
self.sig_msg.emit('Saving data '
'from thread "{}" (#{})'.format(thread_name,
thread_id))
print(self, "running SaveThread")
h5f = h5py.File(self.filename, 'w')
h5f.create_dataset('data',
data=self.data,
compression='gzip',
compression_opts=self.compression_level)
h5f.close()
self.sig_done.emit(self.filename)
''' End Class '''
答案 0 :(得分:1)
这里实际上有两个问题:(1)Qt的信号和插槽机制,以及(2)h5py
。
首先是信号/插槽。这些实际上通过传递给信号的复制参数来工作,以避免任何竞争条件。 (这只是你在Qt C ++代码中看到许多带有指针参数的信号的原因之一:复制指针很便宜。)因为你在主线程中生成数据,所以必须在主线程中复制它线程的事件循环。数据显然足够大,需要一些时间,阻止事件循环处理GUI事件。如果您(为了测试目的)在Saver.save_data()
槽内生成数据,则GUI仍然保持响应。
但是,在打印第一条"Saving data from thread..."
消息后,您现在会注意到一个小的延迟,表示在实际保存期间主事件循环被阻止。这是h5py
的用武之地。
您可能会在文件顶部导入h5py
,这是&#34;正确的&#34;要做的事。我注意到如果你在创建文件之前直接import h5py
,那就消失了。我最好的猜测是涉及全局解释器锁,因为h5py
代码可以从主线程和保存线程中看到。我原本期望主线程完全在Qt模块代码中,但是,GIL无法控制。所以,就像我说的那样,我不确定是什么原因造成阻塞。
就解决方案而言,如果你可以做我在这里描述的那样,那将解决问题。如果可能,建议在主线程外部生成数据。也可以将一些memoryview
对象或numpy.view
对象传递给保存线程,但是您必须自己处理线程同步。另外,在h5py
广告位中导入Saver.save_data()
会有所帮助,但如果您需要代码中的其他位置的模块,则不可行。
希望这有帮助!