Qt: Использование потока для создания виджетов

В моем приложении у меня есть QWidget MyWidget, который мне нужно создать после нажатия кнопки. Итак, MyWidget — это тяжелый виджет со многими дочерними компонентами, а у этих дочерних компонентов тоже много компонентов. Теперь, когда я пытаюсь создать виджет после нажатия кнопки, иногда требуется, чтобы виджет появился, что мне не нравится. Итак, мне нужно создать виджет до нажатия кнопки PushButton и держать экземпляр под рукой. Нить кажется идеальной для этой работы. Но я мало знаю о Qt Thread.

Теперь может кто-нибудь предложить мне, как подойти к проблеме и что можно сделать, чтобы решить проблему?

Примечание. Чтобы четко сказать, почему мой виджет такой тяжелый, в MyWidget у меня есть 7 * 24 виджета, каждый из которых имеет сложенный виджет с двумя сложенными на нем виджетами, каждый из которых имеет кнопку.


person Shakib Ahmed    schedule 27.07.2014    source источник
comment
Являются ли эти дочерние компоненты вашими собственными виджетами? Что так долго занимает их инициализацию? Почему вы делаете эту инициализацию в конструкторах?   -  person Kuba hasn't forgotten Monica    schedule 28.07.2014
comment
Какую альтернативную идею я могу использовать?   -  person Shakib Ahmed    schedule 28.07.2014
comment
Вы должны показать код на этом этапе. Код, который мы можем скомпилировать и протестировать. Код, который воспроизводит вашу медлительность   -  person Kuba hasn't forgotten Monica    schedule 28.07.2014


Ответы (2)


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

Вообще говоря, в ваших тяжелых классах виджетов распараллеливайте неграфические, длительные задачи инициализации. Вы можете использовать QtConcurrent::run для этой цели. Конечно, для этой цели вам необходимо сделать вашу реализацию потокобезопасной.

Вы также должны убедиться, что вы категорически не используете какие-либо блокирующие API в коде виджета. Никакой код GUI в целом, но конкретно конструктор виджета, никогда не должен:

  • дождитесь завершения любого доступа к файлам,

  • дождитесь завершения любого сетевого соединения,

  • дождитесь завершения любого доступа к базе данных,

  • использовать любые методы waitForXxxx в Qt,

  • использовать вещи, которые могут косвенно обращаться к файловой системе, такие как QSettings.

person Kuba hasn't forgotten Monica    schedule 28.07.2014
comment
Если вы говорите мне сделать конструктор пользовательского интерфейса потокобезопасным, я сделал его потокобезопасным. Позже пользовательский интерфейс вызывает некоторые классы контроллера, классы-конструкторы от имени контроллера обращаются к базе данных. Это достаточно хорошо? - person Shakib Ahmed; 28.07.2014
comment
@ShakibAhmed Конструктор QWidget по определению не является потокобезопасным, вы не можете сделать его таким. Что вам нужно, чтобы сделать потокобезопасным, так это долго работающий код, который выполняет неграфическую инициализацию, которая предположительно выполняется долго. Затем такой код можно выполнить через QtConcurrent::run — он будет выполняться в рабочем потоке. Вам нужно будет рассказать нам, что так долго работает в ваших конструкторах. - person Kuba hasn't forgotten Monica; 28.07.2014
comment
Чтобы сказать явно, в моем виджете у меня есть 7 * 24 виджета, каждый из которых имеет сложенный виджет с двумя сложенными на нем виджетами, каждый из которых имеет кнопку. - person Shakib Ahmed; 28.07.2014
comment
@ShakibAhmed Отлично. Покажите нам простой пример функции main, которая создает это, в идеале с некоторым временным кодом, чтобы показать синхронизацию этого. Всего должно быть не более 40 строк. В противном случае это обсуждение в вакууме. - person Kuba hasn't forgotten Monica; 28.07.2014

Во-первых, Qt и почти все другие фреймворки (по крайней мере те, что я знаю), которые имеют дело с элементами пользовательского интерфейса, имеют очень специфические требования для поддержки многопоточности.

Qt необходимо, чтобы каждый потомок QObject (например, QWidget) был назначен потоку (см. QObject::thread()).

Это свойство автоматически назначается при создании объекта потоку, в котором создается объект.

Проблема в том, что Qt также требует, чтобы виджет получал события только в том случае, если он принадлежит к тому же потоку, в котором выполняется цикл обработки событий (и он правильно добавлен в родительский виджет).

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

Вы можете переместить QWidget в другой поток, вызвав QObject::moveToThread() метод. Предположим, что mParent — это ваш QMainWindow, а mMyWidget — ваш огромный виджет:

mMyWidget->setParent(0);
mMyWidget->moveToThread(mParent->thread());
mMyWidget->setParent(mParent); // change this to match your layout requirements.

Проверьте QWidget::setParent() для получения дополнительной информации.

Создание вашего огромного виджета должно выполняться не в том же потоке, что и ваш основной поток. Это остается в качестве упражнения, так как это довольно прямолинейно, а документация Qt обширна по потокам.

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

Это очень общий взгляд на проблему, но это только начало, и, поскольку вы не предоставили никакого кода, я даю вам минимум; D

Другой вариант (и, вероятно, лучший) — посмотреть, почему ваш виджет так долго создается, но без дополнительной информации я не могу помочь.

person Vinícius Gobbo A. de Oliveira    schedule 27.07.2014
comment
Я думаю, что вы предполагаете многое, когда предполагаете, что создание экземпляра QWidget в другом потоке не является неопределенным поведением. Насколько я понимаю, любая попытка сделать это — ошибка, которая рано или поздно вас укусит. Если создание вашего виджета занимает так много времени, вы должны передать трудоемкие вещи, не связанные с графическим интерфейсом, помощнику, который выполняется через QtConcurrent::run. Это лучшее, что вы можете сделать. Конечно, некоторые графические вещи можно сделать из любого потока. Например, рисование или загрузка QImages. - person Kuba hasn't forgotten Monica; 28.07.2014
comment
Хотелось бы, конечно, ошибиться здесь. Пожалуйста, покажите какое-нибудь подтверждение (документацию или соответствующий анализ исходного кода Qt), которое указывало бы на то, что создание виджетов в случайном потоке — это нормально. - person Kuba hasn't forgotten Monica; 28.07.2014
comment
На данный момент я рассматриваю этот ответ как серьезно плохой совет. - person Kuba hasn't forgotten Monica; 28.07.2014
comment
Когда я начал изучать Qt, я проверил это. Я хотел узнать о многопоточности и прочем внутри Qt, и я был действительно ужасным программистом (только что начал свой выпускной курс), поэтому я играл (много!) с этими ужасными вещами. Я согласен с вами, это действительно плохой совет, но однажды мне удалось заставить его работать, и с подробностями, которые дал пользователь, сказать особо нечего. - person Vinícius Gobbo A. de Oliveira; 28.07.2014
comment
Проблема в том, что ваш код, работающий без сбоев в течение какого-либо промежутка времени, не является признаком того, что он работает. Невозможно проверить, что это работает. Результаты тестирования, за исключением очень узких обстоятельств, не могут рассматриваться как указание на отсутствие ошибок, они могут использоваться только для указания на наличие ошибок, когда тесты не пройдены. Правильно спроектированный тест всегда будет давать сбой, если вы создаете QWidget в потоках, отличных от графического интерфейса. Это всего лишь вопрос написания такого теста. - person Kuba hasn't forgotten Monica; 28.07.2014