Я столкнулся со странной ошибкой в своем приложении Qt 5.7 (в Windows 10), и обычных виновников такого поведения нигде не найти:
- У перемещаемого объекта есть родитель - скорее всего, это не так
- Попытка вытащить объект в поток вместо того, чтобы толкать его - это причина ошибки, однако я понятия не имею, откуда она берется
Полное сообщение об ошибке
QObject::moveToThread: Текущий поток (0x2afcca68) не является потоком объекта (0x34f4acc8). Невозможно перейти к целевому потоку (0x34f4adc8)
QObject::setParent: Невозможно установить родителя, новый родитель находится в другом потоке
и вот также мой код:
main.cpp
#include <QApplication>
#include <QQuickItem>
#include "CustomQuickWidget.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
const QUrl source = QUrl(QLatin1String("qrc:/main"));
CustomQuickWidget widget(source);
return app.exec();
}
main (псевдоним для main.qml):
// You can put any random QML content in this case really as long as it doesn't create a window since the CustomQuickWidget does that.
Rectangle {
id: window
visible: true
width: 600
height: 480
}
CustomQuickWidget.cpp
#include "CustomQuickWidget.h"
#include <QQuickItem>
CustomQuickWidget::CustomQuickWidget(const QUrl &source, QWidget *parent) : QQuickWidget(source, parent) {
// Setup the recognizer
this->airWheelRecognizer = new QAirWheelGestureRecognizer();
this->airWheelType = QGestureRecognizer::registerRecognizer(airWheelRecognizer);
// and turn on grabbing for all the supported gestures
grabGesture(airWheelType);
grabGesture(Qt::SwipeGesture);
grabGesture(Qt::TapGesture);
// Create thread and device worker
this->deviceThread = new QThread(this);
this->deviceWorker = new DeviceMapper(this, Q_NULLPTR); // NOTE: this here is NOT for parent. The constructor's signature for this class is: DeviceMapper(QObject* receiver, QList<Qt::GestureType>* gestureIDs, QObject* parent = Q_NULLPTR)
this->deviceWorker->init();
// Create timer that will trigger the data retrieval slot upon timeout
this->timer = new QTimer();
this->timer->setTimerType(Qt::PreciseTimer);
this->timer->setInterval(5);
// Move timer and device mapper to other thread
this->timer->moveToThread(this->deviceThread);
this->deviceWorker->moveToThread(this->deviceThread); // FIXME For unknown reason: QObject::moveToThread: Current thread (...) is not the object's thread. Cannot move to target thread
// Connect widget, timer and device mapper
createConnections();
// Run thread
this->deviceThread->start();
// Connect device and start data retrieval
QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleConnection));
QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleRun));
this->show();
}
CustomQuickWidget::~CustomQuickWidget()
{
if (this->deviceThread) {
this->deviceThread->quit();
this->deviceThread->wait();
}
}
void CustomQuickWidget::createConnections()
{
connect(this->timer, SIGNAL(timeout()),
this->deviceWorker, SLOT(slotRetrieveData()));
connect(this->deviceThread, SIGNAL(started()),
this->timer, SLOT(start()));
connect(this->deviceThread, SIGNAL(finished()),
this->deviceWorker, SLOT(deleteLater()));
connect(this->deviceThread, SIGNAL(finished()),
this->deviceThread, SLOT(deleteLater()));
}
bool CustomQuickWidget::event(QEvent* event) {
if (event->type() == QEvent::Gesture) {
bool res = gestureEvent(static_cast<QGestureEvent*>(event)); // Not important so not included as code here
return res;
}
return QWidget::event(event);
}
Как вы можете видеть, у меня здесь происходит типичный рабочий поток. Я убедился, что у моего работника (здесь DeviceMapper
) нет родителя. Он также создается внутри моего виджета (где также создается QThread
), но перемещается в поток вместе с таймером.
Теперь, помимо очевидной проблемы, вынесенной в заголовок, я должен упомянуть следующее:
- Нет такой ошибки при вызове
this->timer->moveToThread(this->deviceThread);
- Тот же самый код без проблем работает в другом проекте, который является поддиректорным проектом — один подпроект создает общую библиотеку (которую я также использую в этом проекте), а другой — приложение, использующее библиотеку.
Единственная разница между моим другим приложением и этим заключается в использовании QQuickWidget
(вместо QWidget
) и QML
. Я новичок в QML
, и это также мой первый QQuickWidget
, поэтому я мог упустить некоторые очевидные настройки, которые необходимо «активировать».
я также добавил
cout << this->deviceWorker->thread()->currentThreadId() << endl;
cout << this->thread()->currentThreadId() << endl;
прямо перед this->deviceWorker->moveToThread(this->deviceThread);
и я получил
0x18b0
0x18b0
это означает, что до moveToThread(...)
мой объект принадлежит тому же потоку, где создается экземпляр QThread
. Печать идентификатора потока после moveToThread(...)
возвращает тот же результат, но это ожидается из-за невозможности правильного перемещения объекта в другой поток.
ОБНОВЛЕНИЕ:
Сообщение об ошибке появляется ТОЛЬКО при сборке в режиме выпуска, однако независимо от типа сборки ошибка все еще присутствует.
QThread::currentThreadId()
— это статическая функция, которая возвращает поток исполняемого в данный момент кода, а не поток, в котором находится объект. Просто отладьтеQObject::thread()
, чтобы узнать, в каком потоке находится объект. - person thuga   schedule 09.08.2016qInstallMessageHandler
и установите там точку останова, а затем подождите, пока вы не получите это предупреждение от Qt. Затем посмотрите в стеке, чтобы проверить, какой QObject на самом деле испускает это. Это может быть какой-то подобъектdeviceWorker
. - person peppe   schedule 09.08.2016threadId
остается 0. Что касается способа @peppe - проблема в том, что сообщение появляется только в режимеrelease
, поэтому я не могу его отладить. Я где-то читал, что могу удалить флаг уровня оптимизации-O3
из сгенерированногоMakefile
, чтобы предотвратить удаление из двоичного файла всей важной отладочной информации, но я не знаю, работает ли это на самом деле. - person rbaleksandar   schedule 09.08.2016-g
, чтобы включить символы отладки. Тот факт, что поведение различается между уровнями оптимизации, указывает на то, что вы где-то вызываете UB, но этот режим отладки не применяет какую-то оптимизацию, которая это обнаруживает. - person underscore_d   schedule 09.08.2016CFLAGS
, так и кCXXFLAGS
(просто чтобы убедиться, что я ничего не пропустил, поэтому теперь у меня есть-pipe -fno-keep-inline-dllexport -ggdb -O2 -std=gnu++11 -frtti -Wall -Wextra -fexceptions -mthreads $(DEFINES)
. Однако я все еще не могу отлаживать. Я устанавливаю флаг в неправильном месте. Я' мы добавили это в файлMakefile.Release
внутри моей библиотекиbuild
, так как это тот, который вызывается изMakefile
верхнего уровня при сборке релиза. - person rbaleksandar   schedule 09.08.2016makefile
также включает его в шаг ссылки, хотя я не совсем уверен, требуется ли это. Вы также должны убедиться, что двоичный файл не содержитstrip
символов, что сведет на нет все ваши усилия! - person underscore_d   schedule 09.08.2016new
/delete
везде обычно не нужны, открывают вас для несчастных случаев и не являются хорошим/современным стилем C++... который так же как и Qt, но в последнее время он, наконец, прилагает надлежащие усилия, если только все устаревшие документы и учебные пособия догонят. Я предлагаю изменить любой объект, который вам не требуется, чтобы пережить область, в которой он объявлен, чтобы иметь продолжительность автоматического/по значению хранения. Это может повлиять или не повлиять на это, но это просто хороший стиль и, вероятно, предотвратит различные другие возможные головные боли. - person underscore_d   schedule 09.08.2016-s -Wl
внутриLFLAGS
, который делает именно это — удаляет из двоичного файла любые символы отладки. Я удалил его, и теперь отладчик срабатывает в режиме выпуска. Спасибо! Что касаетсяnew
/delete
, я стараюсь избегать этого, но когда это происходит, но здесь у меня нет выбора (я преобразовал элементQThread
в стек), поскольку и мой таймер, иdeviceWorker
должны быть динамически выделены. В противном случае, как только конструктор будет завершен, они выйдут за пределы области видимости, и все. :D - person rbaleksandar   schedule 09.08.2016both my timer and the deviceWorker need to be dynamically allocated. Otherwise once the constructor is done they will go out of scope and that's that.
Но это неправда! Они члены класса. Единственное, что может выйти из области видимости в конце конструктора, — это локальные переменные, объявленные в его теле. Если вы зададите этим элементам автоматический срок хранения, они будут жить столько же, сколько и содержащий их объект. - person underscore_d   schedule 09.08.2016DeviceWorker
из неправильного потока (потоков), либоDeviceWorker
владеет некоторыми объектами, которые не являются его дочерними элементами. В любом случае вы обязательно должны опубликовать полный пример, и он должен быть в одном файле. Совершенно бессмысленно иметь отдельные файлы заголовков в таком тестовом примере. Вы также должны продолжать удалять код до тех пор, пока ни один из них не сможет быть удален без исчезновения ошибки. Запихните все вmain.cpp
, оканчивающееся на#include "main.moc"
. См., например. этот ответ для идеи. - person Kuba hasn't forgotten Monica   schedule 09.08.2016new
нужен так же сильно или мало, как это когда-либо было сQObject
и другими классами с семантикой родитель-потомок, аdelete
нужен так же мало, как когда-либо с ними. Это на самом деле совсем не изменилось с современным С++ (за исключением людей, которые действительно понимают, что им не нужно помещать все в кучу отдельно, они могут использовать переменные-члены и т. д.). - person hyde   schedule 16.08.2016