Документация по QObject :: moveToThread () за Qt5.3 объясняет, что moveToThread()
Метод может потерпеть неудачу, если у объекта есть родитель. Как бы я обнаружил эту ошибку в моем коде?
Я понимаю, что достаточно просто убедиться, что у моего объекта нет родителя в первую очередь, возможно, достаточно хорошо, но в качестве защитной практики программирования я хотел бы проверить возвращаемое значение из всех вызовов, которые могут быть неудачными.
РЕДАКТИРОВАТЬ: После некоторых ответов я хочу подчеркнуть, что я полностью осознаю, что могу проверить, равен ли родительский элемент 0, перед вызовом moveToThread. Я ищу возможные способы определить эмпирически, что moveToThread
звонок действительно удался.
Чтобы надежно получить результат moveToThread()
, поймать ThreadChange
событие объекта, находящегося в движении (путем переопределения QObject::event()
или установить фильтр событий) и сохранить, было ли событие замечено в ссылке на локальную переменную:
static bool moveObjectToThread(QObject *o, QThread *t) {
class EventFilter : public QObject {
bool &result;
public:
explicit EventFilter(bool &result, QObject *parent = nullptr)
: QObject(parent), result(result) {}
bool eventFilter(QObject *, QEvent *e) override {
if (e->type() == QEvent::ThreadChange)
result = true;
return false;
}
};
bool result = false;
if (o) {
o->installEventFilter(new EventFilter(result, o));
o->moveToThread(t);
}
return result;
}
Длинная история:
Документация неверна. Вы Можно переместить QObject
с родителем в другой поток. Для этого нужно просто позвонить moveToThread()
на корень из QObject
иерархию, которую вы хотите переместить, и все потомки тоже будут перемещены (это для того, чтобы родители и их дети всегда были в одном потоке). Это академическое различие, я знаю. Просто будьте внимательны.
moveToThread()
Вызов может также потерпеть неудачу, когда QObject
«s thread()
не == QThread::currentThread()
(т.е. вы можете только От себя объект в, но нет вытащить один от другая нить).
Последнее предложение является лежат к детям. Вы Можно вытащить объект, если он был ранее диссоциирован с любым потоком (вызывая moveToThread(nullptr)
,
Когда сродство потока изменяется, объекту отправляется QEvent::ThreadChange
событие.
Теперь ваш вопрос был о том, как надежно определить, что переезд произошел. Ответ: это не просто. Очевидное первое, сравнивая QObject::thread()
возвращаемое значение после moveToThread()
обратиться к аргументу moveToThread()
не очень хорошая идея, так как QObject::thread()
не является (задокументировано) поточно-ориентированным (ср. реализация).
Почему это проблема?
Как только moveToThread()
возвращается, перемещенный поток, возможно, уже начал выполнять «объект», т.е. события для этого объекта. В рамках этой обработки объект может быть удален. В этом случае следующий вызов QObject::thread()
в исходной теме будет разыменовано удаленные данные. Или новый поток передаст объект другому потоку, и в этом случае чтение переменной-члена в вызове thread()
в исходном потоке будет состязаться с записью в ту же переменную-член в moveToThread()
в новой теме.
Итог: доступ к moveToThread()
Объект ed из исходного потока имеет неопределенное поведение. Не делай этого.
Единственный путь вперед — это использовать ThreadChange
событие. Это событие отправляется после проверки всех случаев сбоя, но, что еще важнее, все еще из исходного потока (см. реализация; также было бы просто неправильно отправлять такое событие, если на самом деле не произошло никакого изменения потока).
Вы можете проверить наличие события, разместив подклассы объекта, к которому перемещаетесь, и переопределив QObject::event()
или установив фильтр событий на объект для перемещения.
Конечно, подход с использованием фильтра событий приятнее, поскольку вы можете использовать его для любого QObject
не только те, которые вы можете или хотите подкласс. Однако есть проблема: как только событие отправлено, обработка события переключается на новый поток, поэтому объект фильтра событий будет забиваться из двух потоков, что никогда не является хорошей идеей. Простое решение: сделать фильтр событий дочерним по отношению к перемещаемому объекту, тогда он будет перемещен вместе с ним. Это, с другой стороны, дает вам проблему управления временем жизни хранилища, чтобы вы могли получить результат, даже если перемещенный объект немедленно удаляется, когда он достигает нового потока. Короче говоря: хранилище должно быть ссылкой на переменную в старом потоке, а не на переменную-член перемещаемого объекта или фильтр событий. Тогда все обращения к хранилищу происходят из исходной нити, и нет никаких гонок.
Но, но … разве не так еще небезопасным? Да, но только если объект перемещен снова в другую ветку. В этом случае фильтр событий получит доступ к хранилищу из первого перенесенного потока, и он будет состязаться с доступом на чтение из исходного потока. Простое решение: удалите фильтр событий после его срабатывания. Эта реализация оставлена в качестве упражнения для читателя 🙂
QObject::moveToThread
не удается, только если у него есть родитель. Если его родитель NULL
тогда вы можете переместить это, иначе вы не можете.
РЕДАКТИРОВАТЬ:
Что вы можете сделать, это вы можете проверить поток объекта сродство после того как ты позвонил moveToThread
позвонив QObject :: нить и проверка, действительно ли это изменило свою близость.
QThread *pThread = new QThread;
QObject *pObject = new QObject;
{
QMutexLocker locker(&mutex);
pObject->moveToThread(pThread);
if(pObject->thread() != pThread)
{
qDebug() << "moveToThread failed.";
}
}