Как обнаружить ошибку QObject :: moveToThread () в Qt5?

Документация по QObject :: moveToThread () за Qt5.3 объясняет, что moveToThread() Метод может потерпеть неудачу, если у объекта есть родитель. Как бы я обнаружил эту ошибку в моем коде?

Я понимаю, что достаточно просто убедиться, что у моего объекта нет родителя в первую очередь, возможно, достаточно хорошо, но в качестве защитной практики программирования я хотел бы проверить возвращаемое значение из всех вызовов, которые могут быть неудачными.

РЕДАКТИРОВАТЬ: После некоторых ответов я хочу подчеркнуть, что я полностью осознаю, что могу проверить, равен ли родительский элемент 0, перед вызовом moveToThread. Я ищу возможные способы определить эмпирически, что moveToThread звонок действительно удался.

3

Решение

Чтобы надежно получить результат 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;
}

Длинная история:

  1. Документация неверна. Вы Можно переместить QObject с родителем в другой поток. Для этого нужно просто позвонить moveToThread() на корень из QObject иерархию, которую вы хотите переместить, и все потомки тоже будут перемещены (это для того, чтобы родители и их дети всегда были в одном потоке). Это академическое различие, я знаю. Просто будьте внимательны.

  2. moveToThread() Вызов может также потерпеть неудачу, когда QObject«s thread() не == QThread::currentThread() (т.е. вы можете только От себя объект в, но нет вытащить один от другая нить).

  3. Последнее предложение является лежат к детям. Вы Можно вытащить объект, если он был ранее диссоциирован с любым потоком (вызывая moveToThread(nullptr),

  4. Когда сродство потока изменяется, объекту отправляется QEvent::ThreadChange событие.

Теперь ваш вопрос был о том, как надежно определить, что переезд произошел. Ответ: это не просто. Очевидное первое, сравнивая QObject::thread() возвращаемое значение после moveToThread() обратиться к аргументу moveToThread() не очень хорошая идея, так как QObject::thread() не является (задокументировано) поточно-ориентированным (ср. реализация).

Почему это проблема?

Как только moveToThread() возвращается, перемещенный поток, возможно, уже начал выполнять «объект», т.е. события для этого объекта. В рамках этой обработки объект может быть удален. В этом случае следующий вызов QObject::thread() в исходной теме будет разыменовано удаленные данные. Или новый поток передаст объект другому потоку, и в этом случае чтение переменной-члена в вызове thread() в исходном потоке будет состязаться с записью в ту же переменную-член в moveToThread() в новой теме.

Итог: доступ к moveToThread()Объект ed из исходного потока имеет неопределенное поведение. Не делай этого.

Единственный путь вперед — это использовать ThreadChange событие. Это событие отправляется после проверки всех случаев сбоя, но, что еще важнее, все еще из исходного потока (см. реализация; также было бы просто неправильно отправлять такое событие, если на самом деле не произошло никакого изменения потока).

Вы можете проверить наличие события, разместив подклассы объекта, к которому перемещаетесь, и переопределив QObject::event() или установив фильтр событий на объект для перемещения.

Конечно, подход с использованием фильтра событий приятнее, поскольку вы можете использовать его для любого QObjectне только те, которые вы можете или хотите подкласс. Однако есть проблема: как только событие отправлено, обработка события переключается на новый поток, поэтому объект фильтра событий будет забиваться из двух потоков, что никогда не является хорошей идеей. Простое решение: сделать фильтр событий дочерним по отношению к перемещаемому объекту, тогда он будет перемещен вместе с ним. Это, с другой стороны, дает вам проблему управления временем жизни хранилища, чтобы вы могли получить результат, даже если перемещенный объект немедленно удаляется, когда он достигает нового потока. Короче говоря: хранилище должно быть ссылкой на переменную в старом потоке, а не на переменную-член перемещаемого объекта или фильтр событий. Тогда все обращения к хранилищу происходят из исходной нити, и нет никаких гонок.

Но, но … разве не так еще небезопасным? Да, но только если объект перемещен снова в другую ветку. В этом случае фильтр событий получит доступ к хранилищу из первого перенесенного потока, и он будет состязаться с доступом на чтение из исходного потока. Простое решение: удалите фильтр событий после его срабатывания. Эта реализация оставлена ​​в качестве упражнения для читателя 🙂

5

Другие решения

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.";
}
}
2

По вопросам рекламы [email protected]