【问题标题】:Qt Communication betwen threads, app designQt 线程之间的通信,应用程序设计
【发布时间】:2015-08-25 07:59:02
【问题描述】:

我正在开发一个应用程序,它进行一些 TCP 通信,使用数据库并有一个 GUI(很常见)。在互联网上尝试使用数据库时,我注意到 GUI 响应缓慢,这促使使用线程来处理后端。我现在正在玩这个,并考虑重新设计一个完整的应用程序来充分解决这个问题。所以我想把 GUI(一个从 QMainWindow 派生的类)和后端的东西(一个域​​类,从 QObject 派生)分开。这个领域类看起来像(我这么说是因为我显然不是模式的权威——总是在学习)一个外观模式。想法是在main函数中构造两个对象,然后将DomainClass的指针传递给MainWindow,即MainWindow(DomainClass *domain)。

然后就是这个问题的重点。我正在想象在域中构建许多(实际上不是很多)对象,并使它们通过信号/插槽机制进行通信。像下面这样:

QThread* threadDB = new QThread;
m_database = new Database;
m_database->moveToThread(threadDB);
threadDB->start();

QThread* threadTM = new QThread;
m_tm = new TM;
connect(m_database, &Database::dbConnected, m_tm, &TM::onDbConnected);
m_tm->moveToThread(threadTM);
connect(threadTM, &QThread::started, m_tm, &TM::init);
threadTM->start();

但我得到以下信息:

QObject::connect: Cannot queue arguments of type 'QSqlDatabase'
(Make sure 'QSqlDatabase' is registered using qRegisterMetaType().)

我注意到如果我将 m_tm 排除在线程之外,它会正常工作。像下面这样:

QThread* threadDB = new QThread;
m_database = new Database;
m_database->moveToThread(threadDB);
threadDB->start();

m_tm = new TM;
connect(m_database, &Database::dbConnected, m_tm, &TM::onDbConnected);

这样的设计合理吗?可以考虑哪些替代方案?

【问题讨论】:

标签: qt


【解决方案1】:

有两个问题。首先,运行时错误会告诉您究竟出了什么问题。您正在尝试按值传递数据库实例。而是通过指针传递它。 Qt 元类型系统知道如何处理这些指针。

class Database {
  QSqlDatabase m_db;
  ...
public:
  Q_SIGNAL void dbConnected(QSqlDatabase*);
  ...
};

其次,您只能从创建它的线程 (doc) 中使用 QSqlDatabase 实例。因此,将它传递给生活在不同线程中的对象是没有意义的。您应该将TM 放在与Database 对象相同的线程中,或者您应该让Database 对象进一步封装数据库,只公开一个信号槽接口,然后可以通过使用从任何线程使用线程安全的排队调用。

最后,您不应该使用“每个对象一个线程”模式。您必须能够给出一个由测量支持的理由,证明它是有用的/有帮助的。线程的激增是一件坏事——你永远不应该拥有比你拥有的内核更多的东西。

【讨论】:

  • “您正在尝试按值传递数据库实例”实际上我是通过引用传递它(请参阅我对问题的评论)。总之,无所谓。通过指针传递它可以解决问题。我应该在插槽一侧制作onDbConnected(QSqlDatabase * const db) 吗?我应该在信号端制作void dbConnected(QSqlDatabase * db) const吗?
  • 与我之前的评论相反,现在我有一个支持引用样式的论点,而不是指针样式。抛出运行时错误是因为在不同线程之间建立了连接(这是重要的事情,所以我将错误视为一种确认,我正在朝着正确的方向前进 - 使用指针样式我们会失去这种确认),只需使用qRegisterMetaType<QSqlDatabase>("QSqlDatabase"); 让 QMetaType 知道这一点
  • @KcFnMi “我通过引用传递它” 当使用排队连接时,线程之间是不可能的。排队的连接需要创建任意数量的对象副本,并且您实际上是通过值传递给每个连接的插槽。
  • @KcFnMi 无论如何,我已经说过你不能这样做。这不应该工作有一个很好的理由 - 如果您需要通过信号槽连接传递QSqlDatabase,那么您的设计很可能会被破坏。在这种情况下,情况就是如此。
【解决方案2】:

我将这个答案作为有更多空间的答案,但考虑到@Kuba Ober 的答案,作为评论可能很难。

为了解决第二个问题(@Kuba Ober 指出),我可以将 DomainClass 完全移动到另一个线程吗?在构造函数上编写以下代码:

QThread* thread = new QThread;
this->moveToThread(thread);
connect(thread, &QThread::started, this, &DomainClass::init);
thread->start();

并在 init() 上编写以下代码:

m_database = new Database;
m_tm = new TM;
connect(m_database, &Database::dbConnected, m_tm, &TM::onDbConnecte

【讨论】:

  • 当然。请记住,startedinit 成语完全是可选的。如果对象将总是在您进入其线程后立即对其进行初始化,那么您最好不要为那个习惯用语而烦恼。回想一下QObject 是自引导的:他们不需要任何人向他们发送任何信号来让他们继续前进。在DomainClass 的构造函数中,启动一个零超时计时器,并在该计时器触发后执行初始化工作。这就是 QObject 引导成语。
  • this->moveToThread 是一个可怕的反模式。您人为地限制了该实例的所有者可以使用它执行的操作。只有它的所有者——必须在类的实现之外——才能将它移动到另一个线程。例如,可能所有者将与您的类一起移动到另一个线程 - 如果对象之间存在父子关系,则必须以原子方式完成此类移动。
【解决方案3】:

关于@Kuba Ober cmets...

已将线程管理作业移至域的所有者,因此:

QThread *thread = new QThread;
domain->moveToThread(thread);
thread->start();

更改了域的构造函数:

QTimer::singleShot(0, this, SLOT(init()));

效果很好:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多