如何通过QSqlQueryModel异步查询? [英] How can I query asynchronously via a QSqlQueryModel?

查看:101
本文介绍了如何通过QSqlQueryModel异步查询?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望通过 QSqlQueryModel (PyqQt 5/Qt 5.2)异步,因此GUI不会阻塞.如何做到这一点?也许通过多线程?请提供执行此操作的代码.如果异步使用QSqlQueryModel不切实际,请随意提供替代方法(不过应该与QTableView一起使用).

I wish to query a SQL database via QSqlQueryModel (PyqQt 5/Qt 5.2) asynchronously, so that the GUI doesn't block. How can this be accomplished? Maybe through multithreading? Please provide code of how to do this. If using QSqlQueryModel asynchronously isn't practical, feel free to provide alternatives (should be usable with QTableView though).

我的(同步)代码当前如下所示.主脚本bin/app.py加载gui/__ init__.py并执行其 main 方法.依次使用 gui.models.Table 从数据库加载数据.问题是 gui.models.Table 同步查询数据库并同时锁定GUI.

My (synchronous) code currently looks as shown beneath. The main script bin/app.py loads gui/__init__.py and executes its main method. That in turn uses gui.models.Table to load data from the database. The problem is that gui.models.Table queries the database synchronously and locks up the GUI in the meantime.

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()

推荐答案

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

Unfortunately, a typical database driver that Qt (or anyone else, really) uses is synchronous. Qt views unfortunately don't know how to deal with models in foreign threads.

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

The solution thus requires a shim proxy model, subclassing QIdentityProxyModel. The first step in the implementation is to shim all of the source model's method calls with blocking QMetaObject::invokeMethod calls. This is needed just to be correct, if not asynchronous just yet. It' just to expose a safe interface to a model that lives in another thread.

下一步是在某些功能上提供异步贴面.假设您要使 data 方法异步.您要做的是:

The next step is to provide an asynchronous veneer over some of the functionality. Suppose that you want to make the data method asynchronous. What you do is:

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

  1. For each role, have a cache of variant values keyed by the model index.

在源模型的 dataChanged 信号上,缓存所有角色中所有已更改的值. data 调用需要在模型的线程中排队-稍后再讨论.

On the dataChanged signal from the source model, cache all the values that were changed, across all roles. The data call needs to be queued in the model's thread - more on that later.

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

In data, if there's a cache hit, return it. Otherwise return a null variant and queue the data call in the model's thread.

您的代理应该有一个称为 cacheData 的私有方法,该方法将从排队的调用中调用.在另一个答案中,我详细说明了如何在另一个线程中排队函子调用.利用这一点,您的数据调用排队方法可能类似于:

Your proxy should have a private method called cacheData that will be called from the queued calls. In another answer, I've detailed how to queue functor calls in another thread. Leveraging that, your data call queuing method can look like:

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));
  });
}

这只是一个草图.它会涉及很多,但肯定是可行的,并且仍然保持真实模型的语义.

This is just a sketch. It'd be fairly involved, but certainly doable, and still maintaining the semantics of a real model.

这篇关于如何通过QSqlQueryModel异步查询?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆