使用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_())
使用multiprocessing解决PyMuPDF不支持多线程加载导致的界面卡死无响应问题,及一个PyQt5实现的简易PDF阅读器例子的更多相关文章
- 解决ArcGIS API for Silverlight 加载地图的内外网访问问题
原文:解决ArcGIS API for Silverlight 加载地图的内外网访问问题 先上一个类,如下: public class BaseClass { public static string ...
- vs中 VMDebugger未能加载导致异常
,纠结了许久的一个问题,终于找到了解决 vs中 VMDebugger未能加载导致异常 错误号:80004005 搜了好多,没有一个给出完美的答案. 解决办法:工具->导入和导出设置,重置一下 ...
- 解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题
下面两种现象,用同一种方法解决 1.解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题 2.突然有一天首页访问图片很慢,至少隔20多秒所有图片才会出来.(解析:app使 ...
- 解决hibernate中的懒加载(延迟加载)问题
解决hibernate中的懒加载(延迟加载)问题 我们在开发的时候经常会遇到延迟加载问题,在实体映射时,多对一和多对多中,多的一样的属性默认是lazy="true"(即,默认是 ...
- Android 加载gif图片强大框架(支持预加载、缓存,还支持显示静态图片,一行代码全搞定)
之前项目中没有涉及到显示gif图片的功能,也没有着重研究过,最近项目中要用到显示gif图片,于是就在网上一顿搜,用过之后发现如下几个缺点. 1.加载大的gif图片会出现oom. 2.没有预加载和缓存功 ...
- 解决tableView中cell动态加载控件的重用问题
解决tableView中cell动态加载控件的重用问题 tableView的cell,有时候需要在运行时取得对应的数据后才能够动态的创建该cell中的控件并加载到该cell中,此时,你一定会遇到重用问 ...
- 支持无限加载的js图片画廊插件
natural-gallery-js是一款支持无限加载的js图片画廊插件.该js图片画廊支持图片的懒加载,可以对图片进行搜索,分类,还可以以轮播图的方式来展示和切换图片. 使用方法 在页面中引入下面的 ...
- 新浪微博客户端(13)-使用UIWebView加载OAuth授权界面
使用UIWebView加载OAuth授权界面 DJOAuthViewController.m #import "DJOAuthViewController.h" @interfac ...
- cocos2d-x游戏开发(十五)游戏加载动画loading界面
个人原创,欢迎转载:http://blog.csdn.net/dawn_moon/article/details/11478885 这个资源加载的loading界面demo是在玩客网做逆转三国的时候随 ...
随机推荐
- ★Java面向对象(一)——————————基本概念
package boll; /* 用Java语言对现实生活中的事物进行描述. 通过类的形式来体现, 怎么描述呢? 对于事物的描述通常只有两个方面,一个是属性,一个是行为. 只要明确该事物的行为和属性并 ...
- Assembly之instruction之Indirect Autoincrement Mode
Assembler Code Content of ROMMOV @R10+,0(R11) MOV @R10+,0(R11) Length: One or two words Operation: ...
- JavaScript+CSS交互
当鼠标移动到小图片上时,小图片显示红色边框并在上面大图片显示相应大图片,效果如图: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Trans ...
- dubbo之结果缓存
结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓存,以减少用户加缓存的工作量. lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存. threadlocal 当前线程缓存,比如 ...
- 教材配套PPT初稿
1-10章初稿,基本完整.有些粗糙,后面可能会稍作调整. 附更新情况如下: 1.增加了第10章内容: 2.第5章增加了一些内容: 3.第3章内容部分更新: 4.增加了第8-9章内容. 订正:更新了第8 ...
- IronPython中共享的C#基类如何向下转型
在项目中,我们使用IronPython来定义工作流脚本来以应对科研多变的需求.项目使用的主要语言仍然是C#,使用C#封装了各种基础服务与基础设施.Python脚本只使用C#提供的服务,或者说只定义了逻 ...
- Bem命名
BEM思想 1. 什么是BEM: BEM:(Block块,Element元素,Modifier修饰符)一种命名规范, 其核心思想是将页面拆分成一个个独立的富有语义的块(blocks),从而使得团队在开 ...
- 招银网络面试题、考点、知识点总结(Java岗)
java基础 全是基础不用多说肯定考的多,尤其是招银 OOP特性/java语言特性:封装.继承.多态 多态具体的表现:多态应用举例.如何调用父类方法(super).重写和重载(重写父类方法的规则.构造 ...
- 团体程序设计天梯赛-练习集-L1-035. 情人节
L1-035. 情人节 以上是朋友圈中一奇葩贴:“2月14情人节了,我决定造福大家.第2个赞和第14个赞的,我介绍你俩认识…………咱三吃饭…你俩请…”.现给出此贴下点赞的朋友名单,请你找出那两位要请客 ...
- Vue学习之路第二十篇:Vue生命周期函数-组件创建期间的4个钩子函数
1.每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听.编译模板.将实例挂载到 DOM 并在数据变化时更新 DOM 等.同时在这个过程中也会运行一些叫做生命周期钩子的函数 ...