【问题标题】:Creating QPixmaps in a thread在线程中创建 QPixmap
【发布时间】:2017-10-26 04:43:35
【问题描述】:

我在尝试加载大量 png 图像并随后使用 PyQt 显示主题时遇到问题。我当前的工作流程是使用多处理器池来映射一个函数,该函数使用“rb”值打开每个文件,然后将每个文件的字节读入统一列表。最后,父进程通过调用 QPixmap 对象的fromImageData 方法来显示图像。这种方法似乎工作正常,但每次我在图像之间切换(8K 分辨率)时重绘一个新的像素图非常慢。

我希望为每个图像创建一个像素图并循环通过像素图而不是在每个步骤中使用新图像重新创建相同的像素图可能会更快。为此,我尝试在多进程函数中创建一个像素图,但是这是不允许的,因为线程中没有父 QApp。

我的问题是是否有适当的方法来做到这一点?我也曾想过用 celery/reddis 来做这件事,但我看不出我们有什么不同。为每个图像创建一个新的像素图并使用setPixmap 切换它们甚至是一个可行的选择,还是有更合适的方法来实现这一点?

【问题讨论】:

    标签: python pyqt multiprocessing pyqt5 python-multiprocessing


    【解决方案1】:

    您应该能够使用 QThreadPool 和一些 QRunnables 来执行此操作,它们包装了加载像素图的代码。比如:

    from PyQt5 import QtCore, QtGui
    
    class PixmapLoader(QtCore.QRunnable):
        def __init__(self, filename):
            super().__init__()
            self.filename = filename
        def run(self):
            # Load pixmap at filename 
            # ...
            # then emit in a signal
            loaded.emit(pixmap)
    
        loaded = QtCore.pyqtSignal(QtGui.QPixmap)
    

    然后在主应用程序的某个地方,创建一个线程池,运行加载对象,并处理它们的信号。

    pool = QtCore.QThreadPool()
    loaders = [PixmapLoader(filename) for filename in filenames]
    for loader in loaders:
        loader.loaded.connect(handle_new_pixmap)
        pool.start(loader)
    
    def handle_new_pixmap(QtGui.QPixmap):
       # do stuff with pixmap
    

    这个我没试过,但是因为Qt是处理线程的,所以应该可以很好的利用多线程。

    编辑

    如 cmets 中所述,这是行不通的。我忘记了QRunnable 不继承QObject,并且QPixmaps 不能在主线程之外创建。但是使用图像加载器对象非常简单,将其移动到一个或多个后台线程,在其中加载QImage,然后将其发送到主线程供以后使用。这是经过测试的代码,它将执行基本操作,加载当前目录中的所有 PNG 文件。

    #!/usr/bin/env python3
    
    import os
    
    from PyQt5.QtCore import pyqtSignal, QObject, QThread
    from PyQt5.QtGui import QImage
    from PyQt5.QtWidgets import QApplication
    
    class ImageLoader(QObject):
        loaded = pyqtSignal(str, QImage)
    
        def __init__(self, filename):
            super().__init__()
            self.filename = filename
    
        def on_load_signal(self):
            img = QImage(self.filename)
            self.loaded.emit(self.filename, img)   
    
    
    class LoaderManager(QObject):
        request_img_load = pyqtSignal()
    
        def __init__(self):
            super().__init__()
            self.loaders = list(map(ImageLoader, 
                    filter(lambda f: f.endswith('.png'), os.listdir())))
            self.bg_thread = QThread()
    
            for loader in self.loaders:
                self.request_img_load.connect(loader.on_load_signal)
                loader.loaded.connect(self.handle_img_loaded)
                loader.moveToThread(self.bg_thread)
    
            self.bg_thread.start()
    
        def __del__(self):
            self.bg_thread.quit()
    
        def load_all(self):
            self.request_img_load.emit()
    
        def handle_img_loaded(self, name, img):
            print('File {} of size {} loaded'.format(name, img.byteCount()))
    
    if __name__ == '__main__':
    
        app = QApplication([])
        manager = LoaderManager()
        manager.load_all()
    

    【讨论】:

    • 此代码不起作用。 Qt 不支持在主线程之外创建像素图,所以需要使用QImage 代替。另外,QRunnable 没有继承QObject,所以它不能发出信号。你应该使用QThread
    • @ekhumoro 感谢您指出这一点,我已经更新了我的答案。
    • 我会接受这个答案,因为它是 100% 正确的。不过我想提醒大家,像素图会占用大量内存!
    • 这是一个很好的例子,但基本上没有任何意义,因为它仍然会因为 python GIL 而冻结主线程......为了任何实际利益,都需要使用多处理的方法。
    • @spencer Qt 线程不是 Python 线程,不受 GIL 约束。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 2015-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多