PyQt實(shí)現(xiàn)異步數(shù)據(jù)庫(kù)請(qǐng)求的實(shí)戰(zhàn)記錄
需求
開(kāi)發(fā)軟件的時(shí)候不可避免要和數(shù)據(jù)庫(kù)發(fā)生交互,但是有些 SQL 請(qǐng)求非常耗時(shí),如果在主線(xiàn)程中發(fā)送請(qǐng)求,可能會(huì)造成界面卡頓。這篇博客將會(huì)介紹一種讓數(shù)據(jù)庫(kù)請(qǐng)求變得和前端的 ajax 請(qǐng)求一樣簡(jiǎn)單,且不會(huì)阻塞界面的異步請(qǐng)求方法。
實(shí)現(xiàn)過(guò)程
在實(shí)現(xiàn)異步請(qǐng)求之前,需要先明確一下函數(shù)簽名:
def sqlRequest(
service: str,
method: str,
slot,
params: dict = None
)
各個(gè)參數(shù)的解釋如下:
service: 業(yè)務(wù)名method: 接口名slot: 拿到數(shù)據(jù)后調(diào)用的回調(diào)函數(shù)params: 請(qǐng)求參數(shù)
總體流程如下圖所示,包括子界面發(fā)送請(qǐng)求、數(shù)據(jù)庫(kù)線(xiàn)程處理請(qǐng)求、主界面調(diào)用回調(diào)函數(shù)來(lái)消費(fèi)響應(yīng)結(jié)果三個(gè)步驟。

信號(hào)總線(xiàn)
在 Qt 中,子線(xiàn)程無(wú)法直接更新主界面,只能發(fā)送信號(hào)通知主線(xiàn)程,然后在主線(xiàn)程中更新界面。在之前的博客《如何在 pyqt 中實(shí)現(xiàn)全局事件總線(xiàn)》介紹了信號(hào)總線(xiàn)的使用,通過(guò)引入信號(hào)總線(xiàn),可實(shí)現(xiàn)任意層級(jí)的組件之間的通信。
本文的信號(hào)總線(xiàn)只含有兩個(gè)信號(hào),一個(gè)用來(lái)請(qǐng)求數(shù)據(jù),一個(gè)用來(lái)消費(fèi)數(shù)據(jù):
class SignalBus(QObject):
""" Signal bus """
fetchDataSig = Signal(SqlRequest) # 請(qǐng)求數(shù)據(jù)信號(hào)
dataFetched = Signal(SqlResponse) # 響應(yīng)數(shù)據(jù)信號(hào)
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
發(fā)送請(qǐng)求
子界面中通過(guò)調(diào)用 sqlRequest() 函數(shù)來(lái)發(fā)起異步 SQL 請(qǐng)求,該函數(shù)只是將參數(shù)封裝為 SqlRequest 對(duì)象,然后通過(guò) signalBus 的 fetchDataSig 信號(hào)發(fā)送給數(shù)據(jù)庫(kù)子線(xiàn)程:
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)
比如下圖中商品類(lèi)型下拉框的數(shù)據(jù)就來(lái)自于數(shù)據(jù)庫(kù):

在組件 LicenseCard 中使用下述代碼就能完成數(shù)據(jù)的請(qǐng)求和消費(fèi)(組件庫(kù)參見(jiàn) https://qfluentwidgets.com/zh/ ):
from qfluentwidgets import HeaderCardWidget, ComboBox
class LicenseCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__("許可證", parent)
self.goodsComboBox = ComboBox(self)
# 請(qǐng)求商品信息
sqlRequest("goodsService", "listAll", self.onGoodsFetched)
def onGoodsFetched(self, goods: List[Goods]):
""" 將商品信息添加到下拉框中 """
for good in goods:
self.goodsComboBox.addItem(good.name, userData=good)
處理請(qǐng)求
子線(xiàn)程 DatabaseThread 中維護(hù)著一個(gè)請(qǐng)求隊(duì)列 tasks,每當(dāng)收到信號(hào)總線(xiàn)的 fetchDataSig 信號(hào)時(shí),就會(huì)使用反射機(jī)制將請(qǐng)求中攜帶的 service 和 method 字符串轉(zhuǎn)換為數(shù)據(jù)庫(kù)業(yè)務(wù)類(lèi)的方法指針,并將這個(gè)指針添加到隊(duì)列中等待調(diào)用。調(diào)用方法返回的數(shù)據(jù)會(huì)被封裝為 SqlResponse 對(duì)象,接著通過(guò)信號(hào)總線(xiàn)發(fā)送給主界面。
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()
# 處理請(qǐng)求信號(hào)
signalBus.fetchDataSig.connect(self.onFetchData)
def run(self):
""" 處理請(qǐng)求 """
while self.tasks:
task, request = self.tasks.popleft()
result = task(**request.params)
signalBus.dataFetched.emit(SqlResponse(result, request.slot))
def onFetchData(self, request: SqlRequest):
""" 將請(qǐng)求添加到隊(duì)列中 """
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)
處理響應(yīng)結(jié)果
主界面中只需將信號(hào)總線(xiàn)的 dataFetched 信號(hào)連接槽函數(shù),然后在槽函數(shù)中對(duì)取出 response 對(duì)象中的數(shù)據(jù),并調(diào)用回調(diào)函數(shù)來(lái)消費(fèi)數(shù)據(jù)即可:
from qfluentwidgets import MSFluentWindow
class MainWindow(MSFluentWindow):
""" 主界面 """
def __init__(self):
super().__init__()
# 處理響應(yīng)結(jié)果
signalBus.dataFetched.connect(self.onDataFetched)
def onDataFetched(self, response: SqlResponse):
if response.slot:
response.slot(response.data)到此這篇關(guān)于PyQt實(shí)現(xiàn)異步數(shù)據(jù)庫(kù)請(qǐng)求的實(shí)戰(zhàn)記錄的文章就介紹到這了,更多相關(guān)PyQt異步數(shù)據(jù)庫(kù)請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Python實(shí)現(xiàn)剪切板實(shí)時(shí)監(jiān)控方法解析
這篇文章主要介紹了基于Python實(shí)現(xiàn)剪切板實(shí)時(shí)監(jiān)控方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Python實(shí)現(xiàn)的科學(xué)計(jì)算器功能示例
這篇文章主要介紹了Python實(shí)現(xiàn)的科學(xué)計(jì)算器功能,涉及Python基于數(shù)值運(yùn)算與事件響應(yīng)實(shí)現(xiàn)科學(xué)計(jì)算器功能相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
Python利用smtplib實(shí)現(xiàn)郵件發(fā)送
在當(dāng)今數(shù)字時(shí)代,電子郵件已成為我們生活和工作中不可或缺的一部分,本篇文章將為你講解如何在Python發(fā)送郵件,并為你提供實(shí)現(xiàn)的多種方式,希望對(duì)大家有所幫助2023-06-06
python線(xiàn)程池ThreadPoolExecutor,傳單個(gè)參數(shù)和多個(gè)參數(shù)方式
這篇文章主要介紹了python線(xiàn)程池ThreadPoolExecutor,傳單個(gè)參數(shù)和多個(gè)參數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
python?pandas遍歷每行并累加進(jìn)行條件過(guò)濾方式
這篇文章主要介紹了python?pandas遍歷每行并累加進(jìn)行條件過(guò)濾方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
跟老齊學(xué)Python之集成開(kāi)發(fā)環(huán)境(IDE)
IDE的全稱(chēng)是:Integrated Development Environment,簡(jiǎn)稱(chēng)IDE,也稱(chēng)為Integration Design Environment、Integration Debugging Environment,翻譯成中文叫做“集成開(kāi)發(fā)環(huán)境”,在臺(tái)灣那邊叫做“整合開(kāi)發(fā)環(huán)境”。2014-09-09
出現(xiàn)module 'queue' has no attrib
這篇文章主要介紹了出現(xiàn)module 'queue' has no attribute 'Queue'問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Python?nonlocal關(guān)鍵字?與?global?關(guān)鍵字解析
這篇文章主要介紹了Python?nonlocal關(guān)鍵字?與?global?關(guān)鍵字解析,nonlocal關(guān)鍵字用來(lái)在函數(shù)或其他作用域中使用外層變量,global關(guān)鍵字用來(lái)在函數(shù)或其他局部作用域中使用全局變量,更多香瓜內(nèi)容需要的小伙伴可以參考一下2022-03-03
Pygame實(shí)現(xiàn)小球躲避實(shí)例代碼
大家好,本篇文章主要講的是Pygame實(shí)現(xiàn)小球躲避實(shí)例代碼,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽2021-12-12

