本文介绍了QMetaObject::invokeMethod 在...时不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

... 从静态类和非主线程调用.简而言之,我有一个类sapp",它有另一个静态类tobj"作为静态成员.为了避免静态订单初始化失败,tobj 在 sapp 的方法中声明,该方法又返回 tobj 实例的指针.我的问题是,tobj 有一个应该在构造函数中启动的计时器,并且 tobj 可能由非主线程创建.QTimer 不能由主线程以外的线程启动(或者我猜是没有事件循环的线程).出于这个原因,我通过 QMetaObject::invokeMethod + Qt::QueuedConnection 调用 QTimer::start 以避免线程问题,但是它不起作用,QTimer::start 从未被调用.我调查了一些问题,看起来像 QTimer::start 没有被调用,因为 QTimer 的父级(在这种情况下为 tobj)被声明为静态.如果我将 tobj 声明为非静态成员,则一切正常.

... called from a static class and non-main thread.In short, I have a class "sapp", which has another static class "tobj" as a static member. To avoid static order initialization fiasco, tobj is declared inside sapp's method, which in turn, returns pointer of tobj's instance.My problem is that, tobj has a timer which should be started in the constructor, and tobj may be created by non-main thread. QTimer can't be started by a thread other than main thread (or the one which doesn't have event loop i guess).for that reason, I invoke QTimer::start via QMetaObject::invokeMethod + Qt::QueuedConnection to avoid thread problem, however it doesn't work, QTimer::start is never invoked. I investigated a bit the problem and looks like, QTimer::start is not invoked because QTimer's parent(tobj in this case) is declared as static. If I declare tobj as a non static member, everything works fine.

我不太了解 Qt 的内部结构,这可能是一个错误还是我做错了什么?

I don't understand quite well the internals of Qt, could this be a bug or I'm doing something wrong?

代码如下:

class tobj : public QObject
{
    Q_OBJECT

    QTimer timer;
private slots:
        void timeout();

public:
    tobj();
};

class sapp : public QObject
{
    Q_OBJECT

public:
    static tobj* f();
};


void tobj::timeout()
{
    qDebug() << "hi";
}

tobj::tobj()
{
    connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
    timer.setInterval(500);
    qDebug() << QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); // returns true, but never invoked.
}

tobj* sapp::f()
{
    static tobj ff;
    return &ff;
}

这是测试项目的链接,由 1 个标头和 1 个 cpp 文件组成 http://dl.dropbox.com/u/3055964/untitled.zip

Here's a link to the test project, consisting of 1 header and 1 cpp file http://dl.dropbox.com/u/3055964/untitled.zip

我正在 Qt 4.8.0 和 MSVC 2010 上进行测试.

I'm testing on Qt 4.8.0 and MSVC 2010.

非常感谢,非常感谢您的帮助.

Thank you very much, your help is much appreciated.

推荐答案

我认为你做得太过分了.Qt 的美妙之处在于,您尝试做的事情非常简单.

I think you're overdoing it. Qt's beauty lies in the fact that what you try to do is very easy.

您似乎认为 QTimer 会以某种方式神奇地中断您正在运行的线程以执行回调.在 Qt 中从来没有这种情况.在 Qt 中,所有事件和信号都被传递到线程中运行的事件循环,并且该事件循环将它们分派给 QObjects.因此,在一个线程内,由于 Qt 的事件和信号/槽框架,没有并发风险.此外,只要您依赖 Qt 的信号槽和事件机制在线程之间进行通信,您就不需要使用任何其他访问控制原语,因为每个线程内的事物都是序列化的.

You seem to think that a QTimer will somehow magically interrupt your running thread to execute the callback. This is never the case in Qt. In Qt, all events and signals are delivered to the event loop running in the thread, and that event loop dispatches them to QObjects. Thus within a thread, there are no concurrency hazards due to Qt's event and signal/slot framework. Also, as long as you depend on Qt's signal-slot and event mechanisms to communicate between threads, you don't need to use any other access control primitives as within each thread things are serialized.

因此,您的问题是您从未在线程中运行事件循环,因此永远不会拾取超时事件并将其分派到您已连接到 timeout() 信号的插槽.

So, your problem is that you never run an event loop in your thread, so the timeout event will never be picked up and dispatched to the slot you've connected to the timeout() signal.

一个QTimer可以在任何线程中创建和启动,只要你启动它的线程是定时器的QObject所在的线程.QObjects属于您在其中创建它们的线程,除非您使用 QObject::moveToThread(QThread*) 将它们移动到不同的线程.

A QTimer can be created and started in any thread, as long as the thread where you start it is the thread that the timer's QObject lives in. QObjects belong to a thread you create them in, unless you move them to a different thread using QObject::moveToThread(QThread*).

使用invokeMethod 来启动计时器是完全没有必要的.毕竟,您是从它所在的同一线程启动计时器.顾名思义,排队的信号槽连接会将信号排入队列.具体来说,当您发出信号时,QMetaCallEvent 将排队等待接收器插槽所在的 QObject.一个事件循环必须在槽对象的线程中运行以获取它并执行调用.您永远不会在线程中调用任何东西来清空该队列,因此没有什么可以调用您的 timeout() 槽.

The use of invokeMethod to start the timer is entirely unnecessary. You're starting the timer from the same thread it lives in, after all. A queued signal-slot connection will queue the signals in a queue, as the name implies. Specifically, when you signal, a QMetaCallEvent is queued for the QObject where the receiver slot lives. An event loop must run in the slot object's thread to pick this up and execute the call. You never call anything in the thread to empty that queue, thus there's nothing to call your timeout() slot.

至于静态成员初始化失败,您可以在 main() 或位于主线程中的 QObject 中自由构建整个 T 对象,然后将其移动到新的线程——这就是不使用 QtConcurrent 时的做法.使用 QtConcurrent 时,您的可运行函数可以构造和销毁任意数量的 QObject.

As for the static member initialization fiasco, you're free to build your entire T object in main() or within a QObject that lives in the main thread, and then move it to a new thread -- that's how one would do it when not using QtConcurrent. When using QtConcurrent, your runnable function can construct and destruct any number of QObjects.

要修复您的代码,lambda 应该旋转一个事件循环,因此:

To fix your code, the lambda should spin an event loop, thus:

[] () {
    sapp s;
    s.f();
    QEventLoop l;
    l.exec();
}

下面我附上一个 SSCCE 示例,说明如何在 Qt 中惯用地完成它.如果不想使用 QtConcurrent,则有两个变化:您希望 qApp->exit() 紧跟 exit()线程的事件循环——否则,a.exec() 将永远不会退出(它怎么知道?).您还希望在退出 main() 函数之前在线程上 wait() - 销毁仍然存在的 QThreads 是一种糟糕的风格运行.

Below I attach a SSCCE example of how it'd be idiomatically done in Qt. If one doesn't want to use QtConcurrent, then there'd be two changes: you'd want to qApp->exit() right after you exit() the thread's event loop -- otherwise, a.exec() will never exit (how would it know to?). You'd also want to wait() on the thread before exiting the main() function - it's bad style to destroy QThreads that are still running.

exit() 函数只发出事件循环结束的信号,把它们看作是设置一个标志,而不是自己真正退出任何东西——所以它们不同于 C 风格的 API exit()s.

The exit() functions only signal the event loops to finish, think of them as setting a flag, not really exiting anything by themselves -- so they are different from C-style API exit()s.

请注意,QTimer 本身就是一个 QObject.出于性能原因,如果计时器经常触发,使用 QBasicTimer 会便宜得多,它是 QObject::startTimer() 返回的计时器 ID 的简单包装器.然后您将重新实现 QObject::timerEvent().您可以通过这种方式避免信号槽调用的开销.根据经验,直接(非排队)信号槽调用的成本与将两个 1000 个字符的 QString 连接在一起的成本差不多.请参阅我的基准测试.

Note that a QTimer is a QObject in and of itself. For performance reasons, if the timer fires often, it's much cheaper to use a QBasicTimer that is a simple wrapper around the timer id returned by QObject::startTimer(). You'd then reimplement QObject::timerEvent(). You avoid the overhead of a signal-slot call that way. As a rule of thumb, direct (non-queued) signal-slot calls cost about as much as concatenating two 1000 character QStrings together. See my benchmark.

旁注:如果您要发布简短的示例,直接发布整个代码可能会更容易,这样将来就不会丢失——只要它们都在一个文件中.将 100 行示例代码散布在三个文件中会适得其反.看下面,关键是在filename.cpp的最后加上#include "filename.moc".它还有助于一次性定义和声明所有方法,Java 风格.一切都以保持简短和易于遵循的名义.毕竟这是一个例子.

Side note: if you're posting short examples, it may be easier to post the entire code direcly, so that it's not lost in the future -- as long as it's all in one file. It's counterproductive to have a 100 lines of example code strewn across three files. See below, the key is to #include "filename.moc" at the end of filename.cpp. It also helps to define and declare the methods all at once, Java-style. All in the name of keeping it short and easy to follow. It's an example, after all.

//main.cpp
#include <QtCore/QTimer>
#include <QtCore/QDebug>
#include <QtCore>
#include <QtCore/QCoreApplication>

class Class : public QObject
{
    Q_OBJECT
    QTimer timer;
    int n;
private slots:
    void timeout() {
        qDebug() << "hi";
        if (! --n) {
            QThread::currentThread()->exit();
        }
    }
public:
    Class() : n(5) {
        connect(&timer, SIGNAL(timeout()), SLOT(timeout()));
        timer.start(500);
    }
};

void fun()
{
    Class c;
    QEventLoop loop;
    loop.exec();
    qApp->exit();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QtConcurrent::run(&fun);
    return a.exec();
}

#include "main.moc"

这篇关于QMetaObject::invokeMethod 在...时不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-01 01:21