在不同的线程中拥有 Qt 只读模型和视图
Posted
技术标签:
【中文标题】在不同的线程中拥有 Qt 只读模型和视图【英文标题】:Having Qt read-only model and view in different threads 【发布时间】:2019-06-11 09:28:05 【问题描述】:我从 Qt 文档中了解到,在 Qt 模型/视图框架中,模型及其附加视图应该存在于同一个 (GUI) 线程中。这可能会导致一些不良影响,如下所示。我对QAbstractTableModel
进行了子类化并实现了所需的虚函数。在内部,模型对有大量记录的sqlite数据库进行查询,并通过重新实现的data()
函数将数据相应地提供给附加视图。
现在,在 GUI 中,我有一个 QTableView
,我将其附加到该模型。我还有一个QLineEdit
输入字段。在此字段中打印文本会发出textChanged()
信号,该信号连接到模型的(自定义)query()
插槽。这样,在输入字段中键入一个新字符应该会使用与键入的短语匹配的记录来更新表。
好吧,由于我的数据库很大,我不希望在输入另一个字母后立即更新表 - 更新会等待查询完成,这可能需要一两秒钟。
但令我困扰的是,由于我不得不在同一个 GUI 线程中拥有模型和表格,因此输入字段在每个字母之后也会出现边框,直到表格被更新。我想让它这样我可以在没有冻结效果的情况下键入短语,然后等待表格更新。仅当按 Enter 键输入整个短语时才通知模型对我来说不是一个选项 - 我需要 textChanged()
信号才能工作。
然后我想 - 如果我忽略文档并将模型放入非 GUI 线程中,会不会对 Qt 造成很大的冒犯?令我惊讶的是,它奏效了!现在打字不会冻结,程序也不会崩溃(至少现在是这样)。
所以我的问题是 - 在非 GUI 线程中使用模型是否仍然不安全,并且我的程序可能会在任何一天突然崩溃?我还应该提到我想以只读方式使用该模型。如果我需要更改模型底层的数据,我不会使用视图/委托来完成,我只会向模型的线程发送适当的信号,所有更改都将在该线程中执行。
【问题讨论】:
如果数据库查询很慢,为什么不只移动那部分到一个新线程,为什么要移动整个模型呢?QFileSystemModel
做了这样的事情。
可能是因为我不太明白会有什么不同。比如说,我在一个线程中有一个QTableView
和QAbstractItemTable
,然后我还在另一个线程中创建了第三个对象,比如dataEngine
。这个对象实际上处理 sqlite 查询。要查看我的表中的数据,我仍然需要实现模型的data()
方法,所以在这种情况下,我必须从data()
中寻找另一个线程?我希望有一个涉及beginResetModel
和endResetModel
与dataEngine
线程交互的最小算法。
在考虑线程方法之前,我会考虑一下它慢的原因。请注意,即使您的数据库很大,视图一次也只会显示相对少量的数据,而这个数量会受到您的屏幕尺寸的限制。也许您宁愿让您的查询更快?
只是数据库的结构比较复杂。首先,我有很多记录,其次,查询中有多个连接。所以选择语句可能需要一两秒钟。但这不是我希望在不同线程中处理 sqlite 的唯一原因 - 有时我需要插入大量记录,并且 GUI 在这些时刻也会冻结(即使我使用 sqlite 事务,插入可能会占用一些秒,因为我有特殊的数据验证程序)。
提高性能的另一种(或附加)方法可能是在根据用户输入触发查询/更新之前使用延迟计时器。例如。一个QTimer
,它的超时值很短,在每个textChanged()
上(重新)开始。仅在超时时更新表。无论如何,它增加了消除一些已经过时的查询的优势(因为用户输入了更多字符)。否则,我认为thuga的建议是好的。也可能"lazy loading" 可能很有趣。
【参考方案1】:
想象一下这个删除最后一行的例子:
同步(同一线程)
emit beginRemoveRows(int r = last row)
视图做出反应并删除对 r 的引用
从模型中移除 r
endRemoveRows()
view 知道它可能会重新绘制
异步(不同线程)
emit beginRemoveRows(r)
从模型中移除 r
endRemoveRows()
两个信号都在 GUI 线程的事件队列中。
如果 GUI 事件队列在beginRemoveRows()
之前包含重绘事件,则视图将调用model->data(r)
,您的程序可能会崩溃*。
(*) 或者至少会遇到 data()
实现的安全性,但还有其他事情,例如 QPersistentModelIndex
,您无法控制...
【讨论】:
以上是关于在不同的线程中拥有 Qt 只读模型和视图的主要内容,如果未能解决你的问题,请参考以下文章