如何在 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 = 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)

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

image

在组件 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/ ,以上~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用phpStudy本地快速搭建网站并内网穿透远程访问

文章目录 使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点,测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中,查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2.2 映…

【webpack】初始化

webpack 旧项目的问题下一代构建工具 Vite 主角 :webpack安装webpack1,mode的选项2,使用source map 精准定位错误行数3,使用watch mode(观察模式),自动运行4,使用webpack-dev-server工具,自动刷…

python flask Jinja2模板学习

分类很好的一篇文章 Jinja2模板语法 Jinja2里常见的三种定界符: (1) 语句 {% ... %}(2) 表达式 {{ ... }}(3) 注释 {# ... #} {%set adazhaung%} 语句设置变量{{a}} 表达式{% if 2>1 %}控制语句以{%endif%}结尾 Jinja2支持使用“.”获取变量的属…

ERP软件定制开发对企业的优势|app小程序搭建

ERP软件定制开发对企业的优势|app小程序搭建 随着科技的不断发展,企业管理也面临了更多的挑战。为了更好地适应市场需求和提高运营效率,越来越多的企业开始选择使用ERP软件进行管理。然而,市场上现成的ERP软件并不能完全满足企业的需求&#…

【移动端vant 地址选择滑动不了】

分析: H5页面直接在浏览器打开是没有任何问题的,但是内嵌到小程序中就会出现,目前已出现在抖音,快手,小程序中,其他的没有试 大致看了一下,滑动不了的原因,可能是页面禁止滑动或滚动…

人工智能企业引入S-SDLC,推动安全能力大跃升,保障AI技术体系深化落地

某人工智能公司是国际知名的上市企业,核心技术处于世界前沿水平。多年来,该企业在智慧教育、智慧医疗、智慧城市、智慧司法、金融科技、智能汽车、运营商、消费者等领域进行深度技术赋能,深入推进各个行业的智能化、数字化转型建设。 人工智能…

第二十一 网络通信

网络通信 21.1 网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。 21.1.1 局域网与互联网 实现两台计算机的通信,必须用一个网络线路来连接两台计算机 21.1.2 网络协议 1.ip协议 IP是Internet Protocol的简称,是一种网络协议 2…

使用cpolar完成内网穿刺

cpolar官网上有一句评论:cpolar是用过最简单的内网穿刺工具! 实际体验下来,cpolar确实是能够非常简单地实现内网穿刺 先说弊端,免费版的cpolar提供的穿刺地址,有效期为一天,进程连接数有限,如…

企业软件的分类有哪些|app小程序定制开发

企业软件的分类有哪些|app小程序定制开发 企业软件是指为了满足企业运营和管理需求而开发的软件系统。根据不同的功能和应用领域,企业软件可以分为以下几个分类: 1. 企业资源计划(Enterprise Resource Planning,ERP)软…

非线性成长的科技潮品,Realme“大黑马”之路如何延续?

存量博弈时代,如何从“内卷”中突围,是智能手机市场最大的命题。 12月4日,真我realme副总裁、全球营销总裁、中国区总裁徐起在社交媒体发言:“既然已经在红海市场里,那就血战到底吧!” 这是为12月7日发布的…

python-比较Excel两列数据,并分别显示差异

利用 openpyxl 模块,操作Excel,比较Excel两列数据,并分别显示差异 表格数据样例如下图 A,B两列是需要进行比较的数据(数据源为某网站公开数据);C,D两列是比较结果的输出列 A&#…

Chatgpt如何完成论文写作及python机器学习和深度学习领域的运用

2023年随着OpenAI开发者大会的召开,最重磅更新当属GPTs,多模态API,未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义,不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

初学vue3与ts:vue3选项式api获取当前路由地址

vue2的获取方法 this.$route.pathvue3选项式api获取方法 import { useRouter } from vue-router; const router useRouter(); console.log(router) console.log(router.currentRoute.value.path)

集合的大家族

常见面试题:ArrayList和LinkedList的区别 ArrayList是基于动态数组实现的,而LinkedList是基于链表实现的由于第1点因此ArrayList在查找和修改元素操作时效率更高,而在增删元素时LinkedList则效率更高,因为ArrayList需要移动数据

应用在大功率驱动器中的IGBT晶圆

功率器件驱动器是电力电子系统的低压信号控制电路和高压主电路之间的接口,是功率器件应用的关键技术与难点之一。功率器件中的晶体管和晶闸管在应用中需要驱动器的驱动信号才可运行,功率器件驱动器的通常作用是电气隔离、信号传输与放大及功率器件的保护…

二叉树题目:翻转二叉树以匹配前序遍历

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题:翻转二叉树以匹配前序遍历 出处:971. 翻转二叉树以匹配前序遍历 难度 5 级 题目描述 要求 给定一个二叉树的根结点 root \texttt{roo…

【STM32单片机】简易电子琴设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器,使用数码管模块、矩阵按键、无源蜂鸣器等。 主要功能: 系统运行后,蜂鸣器播放一首音乐,进入电子琴模式,…

“大病来前,脚先知”!若是你的脚部有6个表现,或是大病信号

脚是人的“根”,一棵大树是否繁盛取决于根。当根部枯萎时,树木就会率先枯竭,而脚就是人体老化的征兆。因此,有一句古老的谚语,“大病来临之前脚先知”,这意味着可以通过观察脚的运动表现来预测大病的迹象。…

从零开发短视频电商 JMH压测真实示例DEMO

文章目录 原理依赖基础示例结果main 关键注解示例BenchmarkWarmupMeasurementBenchmarkModeOutputTimeUnitForkThreadsStateSetup 和 TearDownParam 问题DeadCode常量折叠Loops JMH 测试的对象可以是任一方法,颗粒度更小,例如本地方法,Rest A…

【数据结构】手撕排序

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 文章目录 一、排序的概念及其运用1.1 排序的概念1.2 常见的算法排序 二、 冒泡排序三、直接插入排…