如何在 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. 同步网络 ...
随机推荐
- Docker下elasticsearch8部署、扩容、基本操作实战(含kibana)
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇记录了用docker搭建ElasticS ...
- Java将MySQL建表语句转换为SQLite的建表语句
Java将MySQL建表语句转换为SQLite的建表语句 源代码: package com.fxsen.platform.core.util; import java.util.HashMap; im ...
- plt.rcParams运行时修改全局配置参数
plt.rcParams简单介绍 plt.rcParams即 "运行时配置参数"("runtime configuration parameters"),是运行 ...
- 掌握Go的运行时:从编译到执行
讲解Go语言从编译到执行全周期流程,每一部分都会包含丰富的技术细节和实际的代码示例,帮助大家理解. 关注微信公众号[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有10+ ...
- HTML网页/KRPano项目一键打包EXE工具(HTML网页打包成单个windows可执行文件exe)
HTML一键打包EXE工具使用说明 工具简介 HTML一键打包EXE工具(HTML封装EXE,桌件)能把任意HTML项目(网址)一键打包为单个EXE文件,可以脱离浏览器和服务器,直接双击即可运行.支持 ...
- 485modbus转profinet网关连接威纶通与三菱变频器modbus通讯
485modbus转profinet网关连三菱变频器modbus通讯触摸屏监控 本案例介绍了如何通过485modbus转profinet网关连接威纶通与三菱变频器进行modbus通讯.485modbu ...
- Intrusion Detection Using Convolutional Neural Networks for Representation Learning 笔记
Intrusion Detection Using Convolutional Neural Networks for Representation Learning 2.2 实验数据的预处理 为了确 ...
- 中国这么多 Java 开发者,应该诞生出生态级应用开发框架
1.必须要有,不然就永远不会有 应用开发框架,虽然没有芯片.操作系统.数据库.编程语言这些重要.但是最终呈现在用户面前的,总是有软件部分.而软件系统开发,一般都需要应用开发框架,它是软件系统的基础性部 ...
- Python开发之Django框架
一. Django框架 01.网络软件开发架构演变过程 02.HTTP协议讲解 03.web应用与框架介绍及手撸web框架 04.Django入门项目创建与必会三板斧 05.Django静态文件配置与 ...
- Building Bridges 题解
Building Bridges 题目大意 连接两根柱子 \(i,j\) 的代价是 \((h_i-h_j)^2+\sum\limits_{k=j+1}^{i-1}w_k\),连接具有传递性,求将 \( ...