【问题标题】:How can I query asynchronously via a QSqlQueryModel?如何通过 QSqlQueryModel 进行异步查询?
【发布时间】:2014-04-24 07:47:43
【问题描述】:

我希望通过QSqlQueryModel (PyqQt 5/Qt 5.2) 异步查询 SQL 数据库,这样 GUI 就不会阻塞。如何实现?也许通过多线程?请提供如何执行此操作的代码。如果异步使用 QSqlQueryModel 不实用,请随意提供替代方案(不过应该可以与 QTableView 一起使用)。

我的(同步)代码目前如下所示。主脚本 bin/app.py 加载 gui/__init__.py 并执行其main 方法。这反过来又使用gui.models.Table 从数据库中加载数据。问题是gui.models.Table同步查询数据库,同时锁定了GUI。

bin/app.py:

import os.path
import sys

sys.path.insert(0, os.path.abspath(os.path.join(
    os.path.dirname(__file__), "..")))

import gui


if __name__ == "__main__":
    gui.main()

gui/__init__.py:

import sys
import os.path
from PyQt5 import uic
from PyQt5 import QtCore, QtWidgets

from gui import models


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi(os.path.join(os.path.dirname(__file__), 'app.ui'), self)
        self.tableView.setModel(models.Table(self))


def main():
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec_()

gui/models.py:

import os.path
from PyQt5.QtCore import *
from PyQt5.QtSql import *


class Table(QSqlQueryModel):
    def __init__(self, parent=None):
        super(Table, self).__init__(parent)

        pth = os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
                                           "test.sqlite"))
        db = QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName(pth)
        if not db.open():
            raise Exception("Couldn't open database '{}'".format(pth))
        try:
            self.setQuery("select * from Test")
        finally:
            db.close()

【问题讨论】:

    标签: multithreading qt asynchronous pyqt qt5


    【解决方案1】:

    不幸的是,Qt(或其他任何人,实际上)使用的典型数据库驱动程序是同步的。不幸的是,Qt 视图不知道如何处理外部线程中的模型。

    因此,该解决方案需要一个 shim 代理模型,子类化 QIdentityProxyModel。实现的第一步是使用阻塞QMetaObject::invokeMethod 调用来填充源模型的所有方法调用。这只是为了正确,如果还不是异步的话。这只是为了向存在于另一个线程中的模型公开一个安全接口。

    下一步是在某些功能上提供异步胶合板。假设您想让data 方法异步。你要做的是:

    1. 对于每个角色,都有一个由模型索引作为键的变量值缓存。

    2. 在来自源模型的dataChanged 信号上,缓存所有角色中更改的所有值。 data 调用需要在模型线程中排队 - 稍后会详细介绍。

    3. data 中,如果有缓存命中,则返回它。否则返回一个空变量并将data 调用排队在模型的线程中。

    您的代理应该有一个名为cacheData 的私有方法,该方法将从排队的调用中调用。在另一个答案中,我有detailed how to queue functor calls in another thread。利用这一点,您的数据调用队列方法可能如下所示:

    void ThreadsafeProxyModel::queueDataCall(const QModelIndex & index, int role) {
      int row = index.row();
      int column = index.column();
      void * data = index.internalPointer();
      postMetacall(sourceModel()->thread(), [this, row, column, data, role]{
        QVariant data = sourceModel()->data(createIndex(row, column, data), role);
        QMetaObject::invoke(this, "cacheData", 
                            Q_ARG(QVariant, data), Q_ARG(int, role),
                            Q_ARG(int, row), Q_ARG(int, column), Q_ARG(void*, data));
      });
    }
    

    这只是一个草图。它会相当复杂,但肯定是可行的,并且仍然保持真实模型的语义。

    【讨论】:

      猜你喜欢
      • 2019-01-08
      • 2014-02-17
      • 2021-04-23
      • 1970-01-01
      • 2020-01-16
      • 1970-01-01
      • 1970-01-01
      • 2011-12-03
      • 1970-01-01
      相关资源
      最近更新 更多