[SOLVED] QTest and QObject::killTimer warning
-
I'm still quite new to the Qt framework and I have a question regarding QTest, timers and threads, which despite there being a few posts about already I still find myself unable to answer:
Some of my code requires the use of Windows asynchronous I/O and to not get in conflict with the Qt event loop, I decided to create my own thread using native _beginthreadex. From there I dispatch signals to the QObject using QMetaObject::invokeMethod and specifying Qt::QueuedConnection as is also described in http://qt-project.org/forums/viewthread/13817
At first this was working great, when the object receiving these signals was on the Qt main thread but now I created a separate QThread object (no subclassing) and suddenly I receive these dreaded QObject::killTimer warnings "timers cannot be stopped from a different thread".
The forum posts I found almost always have something to do with subclassing QThread (which I didn't do) and also using timers - which I'm also not using, at least not explicitly. My guess is that something in QTest does.
Since it's a unit test, the participating classes are quite few: my class under test, QSignalSpy and QThread.
Effectively, I suspect either QSignalSpy::wait or QTRY_COMPARE_WITH_TIMEOUT to issue these messages. Yet moving the spy to the other thread doesn't seem to have any effect.If anyone has an idea, it would be greatly appreciated!
-
Sounds like something is being executed in the wrong thread. Can you post the code of your class under test?
Since I haven't seen your code, I can only give general hints:
By default, a QObject lives in the thread that created it
When a QObject is moved to another thread, its child QObjects are moved too
A QObject's member variables do not automatically become its children -- the parent-child relationship needs to be set explicitly, either using the constructor or setParent()
From #1, if an object has QObjects as member variables, those member QObjects live in the thread that created them
From #3, if a QObject has non-child member variables, and the owning QObject is moved to another thread, those member variables are not moved with the owning object
All QObjects have a built-in timer, accessible by QObject::startTimer() and QObject::killTimer
-
Thank you for the quick reply! Indeed it did clear up some blank spots in my understanding of Qt and threading.
Unfortunately the class tested is quite complex so I can't publish it in full here. I'll do some lobbying to make the component OSS once it works, though.
It's an improved version of the QFileSystemWatcher, which, as also written by one of the devs http://blog.rburchell.com/2012/03/qt-51-aka-when-qfilesystemwatcher-might.html is lacking in some features at the moment.
My class uses Win32 ReadDirectoryChangesW asynchronous I/O to monitor directories and emits more specialized signals for created, modified and deleted files.
As a general overview of the architecture, it's like this:QImprovedFileSystemWatcher : public QObject
- has QString and QList members, which don't seem to have moveToThread
- holds DirectoryMonitor, not derived from QObject
- DM evokes signals on owning class using QMetaObject::invokeMethod
TestClass : QObject
- instantiated on main thread
- uses QSignalSpy
- works fine, but complains about kill timer when putting file system watcher on a separate thread
A sample test method, which sometimes gives the warning, sometimes it doesn't:
@void
QImprovedFileSystemWatcherTest::testSingleCreate() {
QSignalSpy createFileSpy(m_watcher, SIGNAL(fileCreated(const QFileInfo&)));
m_watcher->startWatching();auto filePath = m_tempDir->path()+"\testFile.txt";
QFile createdFile(filePath);
QVERIFY2(createdFile.open(QIODevice::WriteOnly), QString("Could not create test file %1").arg(filePath).toUtf8());
createdFile.close();QTRY_COMPARE_WITH_TIMEOUT(createFileSpy.count(), 1, 2000);
QFileInfo argument = createFileSpy.takeFirst().takeFirst().value<QFileInfo>();
QCOMPARE(argument.fileName().toUtf8().constData(), "testFile.txt");
}
@ -
Update: I found a kind of solution to this by removing the separate thread from my test class. Signals are correctly dispatched from the Windows-managed thread to Qt.
But now that I'm using QTRY_COMPARE_WITH_TIMEOUT in my test, it will switch back into the Qt event loop and allow it to process the messages which have been queued for the object which resides in the same thread, letting the signal-spying succeed.So I still suspect there is something fishy with using this macro in conjunction with an object that resides on another thread but I can look into that in my free time.
Thank you again for your helpful insight into the event loop, JKSH!
-
You're welcome :) Thank you for providing details even if you can't post the code -- it certainly makes it easier for others to understand your issue properly!
[quote author="Marcus Ilgner" date="1373373765"]
- has QString and QList members, which don't seem to have moveToThread
[/quote]The concept of "thread affinity":http://qt-project.org/doc/qt-5.1/qtcore/threads-qobject.html only applies to QObjects. In C++, data is equally accessible to all threads -- Non-QObjects don't inherently "belong" to a particular thread, so you can't "move" them between threads.
[quote]TestClass : QObject
- instantiated on main thread
- works fine, but complains about kill timer when putting file system watcher on a separate thread[/quote]This could be a source of your problem.
Just because a QObject lives in a particular thread doesn't mean that all its methods will automatically run in that thread. Queued slots will run in the thread that the QObject lives in, only if the slot is invoked with a signal/event or QMetaObject::invokeMethod(). But, if anything from your main thread directly calls any of the watcher's member functions (including slots), then those functions will run in the main thread.
- has QString and QList members, which don't seem to have moveToThread