PyQt实现异步数据库请求的实战记录

 更新时间:2023年12月08日 08:16:26   作者:之一Yo  
开发软件的时候不可避免要和数据库发生交互,但是有些 SQL 请求非常耗时,如果在主线程中发送请求,可能会造成界面卡顿,本文将介绍一种让数据库请求变得和前端的 ajax 请求一样简单,希望对大家有所帮助

需求

开发软件的时候不可避免要和数据库发生交互,但是有些 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", self.onGoodsFetched)

    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)

到此这篇关于PyQt实现异步数据库请求的实战记录的文章就介绍到这了,更多相关PyQt异步数据库请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python调用外部程序的实操步骤

    python调用外部程序的实操步骤

    在本文里小编给大家分享了关于python如何调用外部程序的步骤和相关知识点,需要的朋友们学习下。
    2019-03-03
  • Pandas读取行列数据最全方法

    Pandas读取行列数据最全方法

    本文主要介绍了Pandas读取行列数据最全方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • Python实现增强版Docx与JSON双向转换的完整指南与代码解析

    Python实现增强版Docx与JSON双向转换的完整指南与代码解析

    在日常办公和软件开发中,我们经常需要处理文档格式转换的需求,本文将详细介绍一个增强版的Python工具,它可以实现Docx与JSON之间的高质量双向转换,有需要的小伙伴可以了解下
    2025-12-12
  • python使用xlrd模块读写Excel文件的方法

    python使用xlrd模块读写Excel文件的方法

    这篇文章主要介绍了python使用xlrd模块读写Excel文件的方法,较为详细的分析了xlrd模块的安装、使用与操作Excel文件的相关技巧,需要的朋友可以参考下
    2015-05-05
  • 详解在Python中创建条形图追赶动画

    详解在Python中创建条形图追赶动画

    动画是使可视化更具吸引力和用户吸引力的好方法。它帮助我们以有意义的方式展示数据可视化。Matplotlib是一个非常流行的数据可视化库,通常用于数据的图形表示以及使用内置函数的动画。本文将用Matplotlib绘制条形图追赶动画,需要的可以参考一下
    2022-03-03
  • Python中Async语法协程的实现

    Python中Async语法协程的实现

    这篇文章主要介绍了Python中Async语法协程的实现,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • Python 基础之字符串string详解及实例

    Python 基础之字符串string详解及实例

    这篇文章主要介绍了Python 字符串(string) 的相关资料,需要的朋友可以参考下
    2017-04-04
  • 用virtualenv建立多个Python独立虚拟开发环境

    用virtualenv建立多个Python独立虚拟开发环境

    这篇文章主要为大家详细介绍了用virtualenv建立多个Python独立虚拟开发环境,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 从基础到高级详解Python读写二进制结构数组的完全指南

    从基础到高级详解Python读写二进制结构数组的完全指南

    二进制数据处理是Python编程中​​至关重要​​的技能,本文将深入探讨Python中读写二进制结构数组的各种方法和技术,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-09-09
  • pandas中groupby操作实现

    pandas中groupby操作实现

    本文主要介绍了pandas中groupby操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02

最新评论