如何在 PyQt 中实现异步数据库请求
需求
开发软件的时候不可避免要和数据库发生交互,但是有些 SQL 请求非常耗时,如果在主线程中发送请求,可能会造成界面卡顿。这篇博客将会介绍一种让数据库请求变得和前端的 ajax 请求一样简单,且不会阻塞界面的异步请求方法。
实现过程
在实现异步请求之前,需要先明确一下函数签名:
def sqlRequest(
service: str,
method: str,
slot,
params: dict = None
)
各个参数的解释如下:
service: 业务名method: 接口名slot: 拿到数据后调用的回调函数params: 请求参数
总体流程如下图所示,包括子界面发送请求、数据库线程处理请求、主界面调用回调函数来消费响应结果三个步骤。

信号总线
在 Qt 中,子线程无法直接更新主界面,必须在子线程中发送信号通知主线程,然后在主线程中更新界面。在之前的博客《如何在 pyqt 中实现全局事件总线》介绍了信号总线的使用,通过引入信号总线,可以在任意层级的之间进行通信。
本文的信号总线只含有两个信号,一个用来请求数据,一个用来消费数据:
class SignalBus(QObject):
""" Signal bus """
fetchDataSig = Signal(SqlRequest) # 请求数据信号
dataFetched = Signal(SqlResponse) # 响应数据信号
signalBus = SignalBus()
class SqlRequest:
""" Sql request """
def __init__(self, service: str, method: str, slot=None, params: dict = None):
self.service = service
self.method = method
self.slot = slot
self.params = params or {}
class SqlResponse:
""" Sql response """
def __init__(self, data, slot):
self.slot = slot
self.data = data
发送请求
子界面中通过调用 sqlRequest() 函数来发起异步 SQL 请求,该函数只是将参数封装为 SqlRequest 对象,然后通过 signalBus 的 fetchDataSig 信号发送给数据库子线程:
def sqlRequest(service: str, method: str, slot=None, params: dict = None):
""" query sql from database """
request = SqlRequest(service, method, slot, params)
signalBus.fetchDataSig.emit(request)
比如下图中商品类型下拉框的数据就来自于数据库:

在许可证卡片 LicenseCard 中使用下述代码就能完成数据的请求和消费(组件库参见 https://qfluentwidgets.com/zh/ ):
from qfluentwidgets import HeaderCardWidget, ComboBox
class LicenseCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__("许可证", parent)
self.goodsComboBox = ComboBox(self)
# 请求商品信息
sqlRequest("goodsService", "listAll", lambda i: self.onGoodsFetched(i))
def onGoodsFetched(self, goods: List[Goods]):
""" 将商品信息添加到下拉框中 """
for good in goods:
self.goodsComboBox.addItem(good.name, userData=good)
处理请求
子线程 DatabaseThread 中维护着一个请求队列 tasks,每当收到信号总线的 fetchDataSig 信号时,就会使用反射机制将请求中携带的 service 和 method 字符串转换为数据库业务类的方法指针,并将这个指针添加到队列中等待调用。调用方法返回的数据会被封装为 SqlResponse 对象,接着通过信号总线发送给主界面。
class DatabaseThread(QThread):
""" Database thread """
def __init__(self, db: QSqlDatabase = None, parent=None):
super().__init__(parent=parent)
self.database = Database(db, self)
self.tasks = deque()
# 处理请求信号
signalBus.fetchDataSig.connect(self.onFetchData)
def run(self):
""" 处理请求 """
while self.tasks:
task, request = self.tasks.popleft()
result = task(**request.params)
signalBus.dataFetched.emit(SqlResponse(result, request.slot))
def onFetchData(self, request: SqlRequest):
""" 将请求添加到队列中 """
service = getattr(self.database, request.service)
task = getattr(service, request.method)
self.tasks.append((task, request))
if not self.isRunning():
self.start()
class Database(QObject):
""" Database """
def __init__(self, db: QSqlDatabase = None, parent=None):
super().__init__(parent=parent)
self.orderService = OrderService(db)
self.userService = UserService(db)
self.goodsService = GoodsService(db)
处理响应结果
主界面中只需将信号总线的 dataFetched 信号连接槽函数,然后在槽函数中对取出 response 对象中的数据,并调用回调函数来消费数据即可:
from qfluentwidgets import MSFluentWindow
class MainWindow(MSFluentWindow):
""" 主界面 """
def __init__(self):
super().__init__()
# 处理响应结果
signalBus.dataFetched.connect(self.onDataFetched)
def onDataFetched(self, response: SqlResponse):
if response.slot:
response.slot(response.data)
总结
在这篇博客中我们使用子线程和信号总线完成了异步数据库请求操作,界面所使用的组件来自于 https://qfluentwidgets.com/zh/ ,以上~~
如何在 PyQt 中实现异步数据库请求的更多相关文章
- 如何在pyqt中自定义无边框窗口
前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...
- 如何在pyqt中实现窗口磨砂效果
磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...
- 如何在pyqt中实现win10亚克力效果
亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...
- 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果
亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...
- 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)
无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...
- 如何在pyqt中给无边框窗口添加DWM环绕阴影
前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...
- 如何在pyqt中实现带动画的动态QMenu
弹出菜单的视觉效果 QLineEdit 原生的菜单弹出效果十分生硬,而且样式很丑.所以照着Groove中单行输入框弹出菜单的样式和动画效果写了一个可以实现动态变化Item的弹出菜单,根据剪贴板的内容是 ...
- 如何在 pyqt 中捕获并处理 Alt+F4 快捷键
前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...
- 如何在 pyqt 中使用动画实现平滑滚动的 QScrollArea
前言 在之前的博客<如何在 pyqt 中实现平滑滚动的 QScrollArea>中,我们使用定时器和队列实现了平滑滚动.但是实现代码还是有一点复杂,所以这篇博客将使用 Qt 的动画框架 Q ...
- Android中的异步网络请求
本篇文章我们来一起写一个最基本的Android异步网络请求框架,借此来了解下Android中网络请求的相关姿势.由于个人水平有限,文中难免存在疏忽和谬误,希望大家可以指出,谢谢大家:) 1. 同步网络 ...
随机推荐
- 【腾讯云 Cloud Studio 实战训练营】提升开发效率与协作:探索腾讯云 Cloud Studio 的强大功能与优势
一.前言 前几天发生了一个故事,发生了这样一个情景:一位新加入的同事刚刚入职不久,领取了一台崭新的电脑.随后,他投身于一个新项目,但却遇到了一个困扰:由于这台电脑没有管理员权限,他无法在上面安装所需的 ...
- Anaconda+PyCharm+Pytorch/tensorflow环境配置个人总结
Anaconda是一个非常方便的python版本管理工具,可以很方便地切换不同版本的Python进行测试.同时不同版本之间也不存在相互的干扰. PyCharm是一款常见的Python IDE,pyto ...
- K8S集群中使用JD KMS服务对敏感数据安全加密
基本概念 KMS,Key Management Service,即密钥管理服务,在K8S集群中,以驱动和插件的形式启用对Secret,Configmap进行加密.以保护敏感数据, 驱动和插件需要使用者 ...
- JOIN 关联表中 ON、WHERE 后面跟条件的区别
SQL中join连接查询时条件放在on后与where后的区别 数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户. 在使用left jion时,on和wh ...
- 数据可视化【原创】vue+arcgis+threejs 实现海量建筑物房屋渲染,性能优化
本文适合对vue,arcgis4.x,threejs,ES6较熟悉的人群食用. 先报备一下版本号 "vue": "^2.6.11" "@arcgis/ ...
- redisson分布式锁的应用——秒杀、超卖 简单例子(分布式锁相关)
1.常见的分布式事务锁 1.数据库级别的锁 乐观锁,给予加入版本号实现 悲观锁,基于数据库的for update实现 2.Redis,基于SETNX.EXPIRE实现 3.Zookeeper,基于In ...
- 4.Autofac依赖注入初使用
前面几篇文章只是初步搭建项目结构,那到底能否运行呢?(能是肯定的啦) 毕竟咱都NetCore了,所以依赖注入要搞起来.专业的解释我就不多说了,很多博客文章说的很详细(其实是我忘了那些术语怎么讲). 按 ...
- Flask框架——Flask脚本、flask知识点补充
文章目录 Flask_脚本 1 集成Python shell 1.1 flask-script的用法: 1.1.1 实例:flask-script的简单实现 1.1.1命令添加方式: 第一种(无参命令 ...
- Python网络编程——基于tcp协议实现远程执行命令、udp协议没有粘包问题、解决粘包问题、socketserver模块的基本使用(基于tcp协议、基于udp协议的使用)
文章目录 基于tcp协议实现远程执行命令 udp协议没有粘包问题 解决粘包问题 解决粘包问题(终极版) socketserver模块的基本使用 基于tcp协议的使用 基于udp协议的使用 基于tcp协 ...
- .NET周刊【10月第1期 2023-10-01】
国内文章 .NET应用如何防止被反编译 https://www.cnblogs.com/Can-daydayup/p/17736700.html 本文主要讲述了如何防止.NET应用被反编译.虽然无法完 ...