zoukankan      html  css  js  c++  java
  • 使用multiprocessing解决PyMuPDF不支持多线程加载导致的界面卡死无响应问题,及一个PyQt5实现的简易PDF阅读器例子

    最近在用PyMuPDF实现一个PDF阅读器,发现PyMuPDF在加载某些epub时耗时非常长,有的长达10几秒,会导致界面卡死无响应。

    尝试用多线程后台加载,发现还是不能解决问题,和作者交流(issue链接 fitz.open blocks main thread even though I use it in a thread)后,作者说该库不支持真正的多线程,在多线程模式下也会阻塞主线程。

    最后用multiprocessing解决该问题,我另外写了一个简单的PyQt5实现的PDF阅读器来说明如何解决该问题,效果图及代码如下。

    #!python3
    # -*- coding: utf-8 -*-
    # this script demostrates how to use PyMuPDF in multiprocessing to avoid unresponsive GUI when fitz.open costs a long time
    # author: yinkaisheng@live.com
    import os
    import sys
    import time
    import multiprocessing as mp
    import queue
    import fitz
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class DocForm(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.process = None
            self.queNum = mp.Queue()
            self.queDoc = mp.Queue()
            self.pageCount = 0
            self.curPageNum = 0
            self.lastDir = ''
            self.timerSend = QtCore.QTimer(self)
            self.timerSend.timeout.connect(self.onTimerSendPageNum)
            self.timerGet = QtCore.QTimer(self)
            self.timerGet.timeout.connect(self.onTimerGetPage)
            self.timerWaiting = QtCore.QTimer(self)
            self.timerWaiting.timeout.connect(self.onTimerWaiting)
            self.initUI()
    
        def initUI(self):
            vbox = QtWidgets.QVBoxLayout()
            self.setLayout(vbox)
    
            hbox = QtWidgets.QHBoxLayout()
            self.btnOpen = QtWidgets.QPushButton('OpenDocument', self)
            self.btnOpen.clicked.connect(self.openDoc)
            hbox.addWidget(self.btnOpen)
    
            self.btnPlay = QtWidgets.QPushButton('PlayDocument', self)
            self.btnPlay.clicked.connect(self.playDoc)
            hbox.addWidget(self.btnPlay)
    
            self.btnStop = QtWidgets.QPushButton('Stop', self)
            self.btnStop.clicked.connect(self.stopPlay)
            hbox.addWidget(self.btnStop)
    
            self.label = QtWidgets.QLabel('0/0', self)
            self.label.setFont(QtGui.QFont('Verdana', 20))
            hbox.addWidget(self.label)
    
            vbox.addLayout(hbox)
    
            self.labelImg = QtWidgets.QLabel('Document', self)
            sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
            self.labelImg.setSizePolicy(sizePolicy)
            vbox.addWidget(self.labelImg)
    
            self.setGeometry(100, 100, 500, 600)
            self.setWindowTitle('PyMuPDF Document Player')
            self.show()
    
        def openDoc(self):
            path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Document", self.lastDir,
                        "All Supported Files (*.pdf;*.epub;*.xps;*.oxps;*.cbz;*.fb2);;PDF Files (*.pdf);;EPUB Files (*.epub);;XPS Files (*.xps);;OpenXPS Files (*.oxps);;CBZ Files (*.cbz);;FB2 Files (*.fb2)", options=QtWidgets.QFileDialog.Options())
            if path:
                self.lastDir, self.file = os.path.split(path)
                if self.process:
                    self.queNum.put(-1) # use -1 to notify the process to exit
                self.timerSend.stop()
                self.curPageNum = 0
                self.pageCount = 0
                self.process = mp.Process(target=openDocInProcess, args=(path, self.queNum, self.queDoc))
                self.process.start()
                self.timerGet.start(40)
                self.label.setText('0/0')
                self.queNum.put(0)
                self.startTime = time.perf_counter()
                self.timerWaiting.start(40)
    
        def playDoc(self):
            self.timerSend.start(500)
    
        def stopPlay(self):
            self.timerSend.stop()
    
        def onTimerSendPageNum(self):
            if self.curPageNum < self.pageCount - 1:
                self.queNum.put(self.curPageNum + 1)
            else:
                self.timerSend.stop()
    
        def onTimerGetPage(self):
            try:
                ret = self.queDoc.get(False)
                if isinstance(ret, int):
                    self.timerWaiting.stop()
                    self.pageCount = ret
                    self.label.setText('{}/{}'.format(self.curPageNum + 1, self.pageCount))
                else:#tuple, pixmap info
                    num, samples, width, height, stride, alpha = ret
                    self.curPageNum = num
                    self.label.setText('{}/{}'.format(self.curPageNum + 1, self.pageCount))
                    fmt = QtGui.QImage.Format_RGBA8888 if alpha else QtGui.QImage.Format_RGB888
                    qimg = QtGui.QImage(samples, width, height, stride, fmt)
                    self.labelImg.setPixmap(QtGui.QPixmap.fromImage(qimg))
            except queue.Empty as ex:
                pass
    
        def onTimerWaiting(self):
            self.labelImg.setText('Loading "{}", {:.2f}s'.format(self.file, time.perf_counter() - self.startTime))
    
        def closeEvent(self, event):
            self.queNum.put(-1)
            event.accept()
    
    def openDocInProcess(path, queNum, quePageInfo):
        doc = fitz.open(path)
        quePageInfo.put(doc.pageCount)
        while True:
            num = queNum.get()
            if num < 0:
                break
            page = doc.loadPage(num)
            pix = page.getPixmap()
            quePageInfo.put((num, pix.samples, pix.width, pix.height, pix.stride, pix.alpha))
        doc.close()
        print('process exit')
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        form = DocForm()
        sys.exit(app.exec_())
  • 相关阅读:
    vi/vim经常使用命令
    微信公众平台开发(数据库连接)
    遍历Map的四种方法
    提高日志质量的 5 大技巧
    位运算 的探究
    STL源代码剖析 读书总结
    从一段代码看fork()函数及其引发的竞争
    oc56--ARC多个对象的内存管理
    oc55--ARC单个对象的内存管理
    oc54--auatorelease应用场景
  • 原文地址:https://www.cnblogs.com/Yinkaisheng/p/10955863.html
Copyright © 2011-2022 走看看