如何在 PyQt 中实现异步数据库请求

需求

开发软件的时候不可避免要和数据库发生交互,但是有些 SQL 请求非常耗时,如果在主线程中发送请求,可能会造成界面卡顿。这篇博客将会介绍一种让数据库请求变得和前端的 ajax 请求一样简单,且不会阻塞界面的异步请求方法。

实现过程

在实现异步请求之前,需要先明确一下函数签名:

def sqlRequest(service: str, method: str, slot, params: dict = None
)

各个参数的解释如下:

  • service: 业务名
  • method: 接口名
  • slot: 拿到数据后调用的回调函数
  • params: 请求参数

总体流程如下图所示,包括子界面发送请求、数据库线程处理请求、主界面调用回调函数来消费响应结果三个步骤。

image

信号总线

在 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 = serviceself.method = methodself.slot = slotself.params = params or {}class SqlResponse:""" Sql response """def __init__(self, data, slot):self.slot = slotself.data = data

发送请求

子界面中通过调用 sqlRequest() 函数来发起异步 SQL 请求,该函数只是将参数封装为 SqlRequest 对象,然后通过 signalBusfetchDataSig 信号发送给数据库子线程:

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)

比如下图中商品类型下拉框的数据就来自于数据库:

image

在组件 LicenseCard 中使用下述代码就能完成数据的请求和消费(组件库参见 https://qfluentwidgets.com/zh/ ):

from qfluentwidgets import HeaderCardWidget, ComboBoxclass 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 信号时,就会使用反射机制将请求中携带的 servicemethod 字符串转换为数据库业务类的方法指针,并将这个指针添加到队列中等待调用。调用方法返回的数据会被封装为 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 MSFluentWindowclass 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/ ,以上~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/255665.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 108. 将有序数组转换为二叉搜索树

对于算法题,按题型类别刷题才会更有成效,因此我这里在网上搜索并参考了下 “🔥 LeetCode 热题 HOT 100” 的题型归类,并在其基础上做了一定的完善,希望能够记录自己的刷题历程,有所收获!点击下发…

【XILINX】ERROR:Place:1136 - This design contains a global buffer instance

记录一个ISE软件使用过程中遇到的问题及解决方案。 芯片:spartan6 问题 ERROR:Place:1136 - This design contains a global buffer instance, , driving the net,>, that is driving the following (first 30) non-clock load pins. This is not a recommended…

《工作绩效报告编制模板》

《工作绩效报告编制模板》 1、项目总体情况 2、项目工作进展 3、后续重点工作 4、后续项目风险和应对方案 软件工程全套资料获取:点我获取

ChatGPT学习笔记

1 ChatGPT架构图 (ChatGPT_Diagram.svg来自于【OpenA | Introducing ChatGPT】) 2 模型训练 ChatGPT在训练时使用了PPO方法;

电商供应链电商采购跨境电商必须知道的1688商品详情API接口接入

item_get-获得1688商品详情 1688.item_get 公共参数 请求地址: 申请调用KEY测试 名称类型必须描述keyString是调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_…

实例分割 Mask-RCNN

参考文章 使用LabelMe标注目标检测数据集并转换为COCO2017格式_labelme转coco-CSDN博客 数据集选择 voc 这次不选择voc,因为文件组织太难了 voc2012文件夹组织 COCO COCO介绍 MC COCO2017年主要包含以下四个任务:目标检测与分割、图像描述、人体关…

Tomcat头上有个叉叉

问题原因: 这是因为它就是个空的tomcat,并没有导入项目运行 解决方案: war模式:发布模式,正式发布时用,将WEB工程以war包的形式上传到服务器 war exploded模式:开发时用,将WEB工程的文件夹直接…

地埋式雨水收集池经过截污挂篮、雨水弃流过滤装置进行过滤

地埋式雨水收集池通常由收集区、过滤区、储存区和控制区组成。在过滤区,地埋式雨水收集池经过截污挂篮、雨水弃流过滤装置进行过滤,确保储存的雨水干净、清澈。 截污挂篮是一种有效的雨水过滤装置,它利用篮子内部的粗糙表面和过滤网&#xf…

C#大型LIS检验信息系统项目源码

LIS系统,一套医院检验科信息系统。它是以数据库为核心,将实验仪器与电脑连接成网,基础功能包括病人样本登录、实验数据存取、报告审核、打印分发等。除基础功能外,实验数据统计分析、质量控制管理、人员权限管理、试剂出入库等功能…

Java程序员,你掌握了多线程吗?(文末送书)

目录 01、多线程对于Java的意义02、为什么Java工程师必须掌握多线程03、Java多线程使用方式04、如何学好Java多线程送书规则 摘要:互联网的每一个角落,无论是大型电商平台的秒杀活动,社交平台的实时消息推送,还是在线视频平台的流…

spring boot项目如何自定义参数校验规则

spring boot项目对参数进行校验时,比如非空校验,可以直接用validation包里面自带的注解。但是对于一些复杂的参数校验,自带的校验规则无法满足要求,此时需要我们自定义参数校验规则。自定义校验规则和自带的规则实现方式一样&…

基于单片机的电子密码锁设计

1.设计任务 利用AT89C51单片机为核心控制元件,设计一个简易的电子密码锁,可设置四位密码,输入错误三次,报警灯亮起(红灯亮起),输入正确,绿灯闪烁三次。可通过LCD显示屏查看密码&…