【问题标题】:QTimer::singleShot and QMetaMethod::invokeQTimer::singleShot 和 QMetaMethod::invoke
【发布时间】:2014-06-10 13:34:11
【问题描述】:

在一些 Qt 示例中,我看到他们使用 QTimer::singleShot(0, this , SLOT(funcA())),为什么不直接调用插槽funcA?使用QMetaMethod::invoke 调用带参数的函数也是同样的问题。

【问题讨论】:

    标签: qt


    【解决方案1】:

    以下几行在功能上都是等效的:

    QTimer::singleShot(0, object, &Class::funcA); // Qt 5
    QTimer::singleShot(0, object, SLOT(funcA())); // Qt 4
    QMetaObject::invokeMethod(object, "funcA", Qt::QueuedConnection); 
    

    现在很明显,目的是在事件循环中执行调用。排队的呼叫导致QMetaCallEvent 发布到object。此事件由QObject::event 处理并导致所需方法的调用。因此,以下是完全等效的,即使后者是私有实现细节 - 让我跳过the details of instantiating the event

    QMetaObject::invokeMethod(object, "funcA", Qt::QueuedConnection);
    QCoreApplication::postEvent(object, new QMetaCallEvent{...});
    

    这在各种情况下都很方便。例如:

    • 在处理所有迄今为止发布的事件后执行一些代码。

    • 仅在事件循环开始后执行。

    • 调用由于 C++ 访问修饰符而无法访问的可调用方法。可调用的方法有:信号、槽和方法声明Q_INVOKABLE

    • QObject 驻留在另一个线程中时,直接调用是不安全的(读取:错误!),除非您明确调用记录为线程安全的方法。

    如果您希望确保事件循环立即退出,排队调用是必要的:如果循环尚未运行,直接quit() 调用是无操作的。

    int main(int argc, char ** argv) {
      QCoreApplication app{argc, argv};
      app.quit(); // this is a no-op since the event loop isn't running yet
      return app.exec(); // will not quit as desired
    }
    
    int main(int argc, char ** argv) {
      QCoreApplication app{argc, argv};
      QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
      return app.exec(); // will return immediately
    }
    

    理想情况下,您应该使用this answer 中的postToThread,它提供了在其他线程中调用方法的最低成本方式:

    int main(int argc, char ** argv) {
      QCoreApplication app{argc, argv};
      postToThread([]{ qApp->quit(); });
    }
    

    另一种方法是使用QObject 作为信号源:

    int main(int argc, char ** argv) {
      QCoreApplication app{argc, argv};
      {
        QObject src;
        src.connect(&src, &QObject::destroyed, &app, &QCoreApplication::quit,
                    Qt::QueuedConnection);
      }
      return app.exec(); // will return immediately
    }
    

    另一种方法是使用自定义事件并在其析构函数中执行:

    int main(int argc, char ** argv) {
      QCoreApplication app{argc, argv};
      struct QuitEvent : QEvent {
        QuitEvent() : QEvent(QEvent::None) {}
        ~QuitEvent() { qApp->quit(); }
      };
      QCoreApplication::postEvent(&app, new QuitEvent);
      return app.exec(); // will return immediately
    }
    

    【讨论】:

    • 在纯 C++ 函数(不在 QTthread 内)中使用 QTimer::singleShot 时,我得到了 QObject::startTimer: Timers can only be used with threads started with QThread,而 QMetaObject::invokeMethod 没有这个问题。有什么方法可以使用支持编译时检查的 QTimer::singleShot (或一些等效方法)?
    • @Isaac 是的。见编辑。 TL; DR of this answer 中给出了最有效和最简单的方法。
    【解决方案2】:

    每个系统都有一个处理事件的事件循环。说喜欢

    application::processEvents()
    {
    // process event list..
    }
    

    现在你写QTimer::singleShot(0, this, SLOT(doSomething())); 的地方可能在某个处理事件中。 当这个循环完成后,将再次调用 processEvents 并执行 doSomething()。

    所以这就像在下一个事件循环中调用 doSomething,而不是立即调用它。 希望你能明白。

    【讨论】:

    • 输入 QCoreApplication::exec 后,您的 gui 线程代码根据定义由于事件(或类似 Unix 信号的异步平台特定机制)而被执行。所以说“可能在某个处理事件中”真的很令人困惑。一旦从main() 调用app.exec(),事件几乎就是any UI 代码执行的唯一原因。
    • 当您在 Qt 中处理任何已发布(而不是已发送)事件时,事件循环的事件分派器位于对 Foo::event(QEvent*) 的最终调用下方的调用堆栈中。当您的代码完成时,控件返回到调度程序,调度程序接收另一个事件,并再次调用目标对象的 event(QEvent*) 方法 - 或者进入睡眠状态等待更多事件的到来。
    • “事件是执行任何 UI 代码的唯一原因”免责声明:尽管有 Unix 信号和 Windows I/O 完成端口。
    【解决方案3】:

    这些方法也可用于从需要公共访问的范围内调用类的受保护成员和私有成员(如果它们被定义为插槽)。

    【讨论】:

      猜你喜欢
      • 2015-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多