需求

开发软件的时候不可避免要和数据库发生交互,但是有些 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 对象,然后通过 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)

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

在许可证卡片 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 信号时,就会使用反射机制将请求中携带的 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 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 中实现异步数据库请求的更多相关文章

  1. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  2. 如何在pyqt中实现窗口磨砂效果

    磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...

  3. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  4. 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果

    亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...

  5. 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)

    无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...

  6. 如何在pyqt中给无边框窗口添加DWM环绕阴影

    前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...

  7. 如何在pyqt中实现带动画的动态QMenu

    弹出菜单的视觉效果 QLineEdit 原生的菜单弹出效果十分生硬,而且样式很丑.所以照着Groove中单行输入框弹出菜单的样式和动画效果写了一个可以实现动态变化Item的弹出菜单,根据剪贴板的内容是 ...

  8. 如何在 pyqt 中捕获并处理 Alt+F4 快捷键

    前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...

  9. 如何在 pyqt 中使用动画实现平滑滚动的 QScrollArea

    前言 在之前的博客<如何在 pyqt 中实现平滑滚动的 QScrollArea>中,我们使用定时器和队列实现了平滑滚动.但是实现代码还是有一点复杂,所以这篇博客将使用 Qt 的动画框架 Q ...

  10. Android中的异步网络请求

    本篇文章我们来一起写一个最基本的Android异步网络请求框架,借此来了解下Android中网络请求的相关姿势.由于个人水平有限,文中难免存在疏忽和谬误,希望大家可以指出,谢谢大家:) 1. 同步网络 ...

随机推荐

  1. Log4j疯狂写日志问题排查

    一.问题是怎么发现的 最近有个 Java 系统上线后不久就收到了磁盘使用率告警,磁盘使用率已经超过了 90% 以上,并且磁盘使用率还在不停增长. 二.问题带来的影响 由于服务器磁盘被打满,导致了系统正 ...

  2. [glibc2.23源码]阅读源码&调试,找出free_hook-0x13分配失败的原因

    0x00 写在前面 发freebuf了:https://www.freebuf.com/articles/endpoint/373258.html 本次阅读源码是本人第一次,算是一个全新的开始.本次看 ...

  3. 5、Mybatis之获取参数值

    5.1.创建新module 5.1.1.右击SSM文件夹,创建新module 5.1.2.选择maven 5.1.3.配置module名称和路径 5.1.4.module初始状态 5.1.5.复制打包 ...

  4. 一文了解Validator库

    1. 引言 github.com/go-playground/validator 是一个 Go 语言的库,用于对结构体字段进行验证.它提供了一种简单而灵活的方式来定义验证规则,并在验证过程中检查结构体 ...

  5. springboot整合feign的接口抽离

    前言 现在很多微服务框架使用feign来进行服务间的调用,需要在服务端和消费端两边分别对接口和请求返回实体进行编码,维护起来也比较麻烦.那有木有一种可能,只用服务端编写接口,客户端像本地方法一样调用, ...

  6. JavaScript动态更新数组

    1.数组的创建var arrayObj = new Array(); //创建一个数组var arrayObj = new Array([size]); //创建一个数组并指定长度,注意不是上限,是长 ...

  7. 二叉树(binary tree)

    二叉树(binary tree) 二叉树(Binary Tree)是一种常见的树状数据结构,它由一组节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点.二叉树具有以下特点: 每个节点最多有 ...

  8. Solution -「CSP 2019」Centroid

    Description Link. 给定一棵 \(n\) 个点的树,设 \(E\) 为边集,\(V'_x,\ V'_y\) 分别为删去边 \((x,y)\) 后 点 \(x\) 所在的树的点集和点 \ ...

  9. 记一次 .NET 某餐饮小程序 内存暴涨分析

    一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序内存异常高,用 vs诊断工具 加载时间又太久,让我帮忙看一下到底咋回事,截图如下: 确实,如果dump文件超过 10G 之后,市面上那些可视化工 ...

  10. Springboot简单功能示例-4 自定义加密进行登录验证

    springboot-sample 介绍 springboot简单示例 跳转到发行版 查看发行版说明 软件架构(当前发行版使用) springboot hutool-all 非常好的常用java工具库 ...