Spec-Zone.ru › Qt 5.11

Потоки и QObjects

QThread наследует QObject. Он излучает сигналы, чтобы указать, что поток начал или закончил выполнение, а также предоставляет несколько слотов.

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

Взаимодействие QObject

QObject является рекурсивным. Большинство его подклассов, не связанных с графическим интерфейсом, таких как QTimer, QTcpSocket, QUdpSocket и QProcess, также являются рекурсивными, что позволяет использовать эти классы одновременно из нескольких потоков. Обратите внимание, что эти классы предназначены для создания и использования в пределах одного потока; создание объекта в одном потоке и вызов его функций из другого потока не гарантированно будет работать. Есть три ограничения, о которых нужно знать:

  • Подкласс QObject всегда должен создаваться в том потоке, где был создан родитель. Это подразумевает, среди прочего, что вы никогда не должны передавать объект QThread (this) в качестве родителя объекта, созданного в потоке (поскольку сам объект QThread был создан в другом потоке).
  • Объекты, управляемые событиями, могут использоваться только в одном потоке. В частности, это относится к механизму таймеров и модулю сети timer mechanism и network module. Например, вы не можете запустить таймер или подключить сокет в потоке, который не является потоком объекта object's thread.
  • Вы должны убедиться, что все объекты, созданные в потоке, удалены перед удалением QThread. Это можно легко сделать, создав объекты на стеке в реализации run().

Хотя QObject является рекурсивным, классы графического интерфейса, особенно QWidget и все его подклассы, не являются рекурсивными. Они могут использоваться только из основного потока. Как отмечалось ранее, QCoreApplication::exec() также должен вызываться из этого потока.

На практике невозможность использования классов графического интерфейса в потоках, отличных от основного потока, легко обойти, поместив трудоемкие операции в отдельный рабочий поток и отобразив результаты на экране в основном потоке, когда рабочий поток завершится. Это подход, используемый для реализации Примера Mandelbrot и Примера блокирующего клиента Fortune.

В целом, создание QObjects до QApplication не поддерживается и может приводить к странным сбоям при выходе, в зависимости от платформы. Это означает, что статические экземпляры QObject также не поддерживаются. Правильно структурированное одно- или многопоточное приложение должно создавать QApplication в первую очередь и последним уничтожать QObject.

Очередь событий для каждого потока

Каждый поток может иметь свою собственную очередь событий. Исходный поток запускает свою очередь событий, используя QCoreApplication::exec(), или, для приложений с графическим интерфейсом с одним диалоговым окном, иногда QDialog::exec(). Другие потоки могут запустить очередь событий, используя QThread::exec(). Как и QCoreApplication, QThread предоставляет функцию exit(int) и слот quit().

Очередь событий в потоке позволяет потоку использовать определенные классы Qt, не связанные с графическим интерфейсом, которые требуют наличия очереди событий (например, QTimer, QTcpSocket и QProcess). Это также позволяет подключать сигналы из любых потоков к слотам определенного потока. Это более подробно объясняется в разделе Сигналы и слоты между потоками ниже.

Threads, objects, and event loops

Экземпляр QObject считается «живущим» в том потоке, в котором он создан. События для этого объекта обрабатываются очередью событий этого потока. Поток, в котором живёт QObject, доступен с помощью QObject::thread().

Функция QObject::moveToThread() изменяет связь потока для объекта и его подклассов (объект нельзя переместить, если у него есть родитель).

Вызов delete объекта QObject из потока, отличного от потока, которому объект принадлежит (или доступ к объекту другими способами) небезопасен, если вы не гарантируете, что объект в данный момент не обрабатывает события. Используйте QObject::deleteLater() вместо этого, и событие DeferredDelete будет опубликовано, и очередь событий потока объекта его eventually обработка. По умолчанию, поток, которому принадлежит QObject, — это поток, в котором был QObject создан, но не после вызова QObject::moveToThread().

Если очередь событий не запущена, события не будут доставлены объекту. Например, если вы создаете объект QTimer в потоке, но никогда не вызываете exec(), QTimer никогда не выпустит свой сигнал timeout(). Вызов deleteLater() также не сработает. (Эти ограничения также применяются к главному потоку.)

Вы можете вручную публиковать события любому объекту в любом потоке в любое время, используя безопасную для потоков функцию QCoreApplication::postEvent(). События автоматически будут обработаны очередью событий потока, в котором был создан объект.

Фильтры событий поддерживаются во всех потоках с ограничением, что объект отслеживания должен жить в том же потоке, что и отслеживаемый объект. Аналогично, QCoreApplication::sendEvent() (в отличие от postEvent()) может быть использовано только для обработки событий объектов, находящихся в потоке, из которого вызывается функция.

Доступ к подклассам QObject из других потоков

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

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

Как и другие объекты, объекты QThread существуют в потоке, где был создан объект — а не в потоке, созданном при вызове QThread::run(). В общем случае небезопасно предоставлять слоты в подклассе QThread, если вы не защищаете переменные-члены с помощью мьютекса.

С другой стороны, вы можете безопасно излучать сигналы из реализации QThread::run(), потому что излучение сигналов является потокобезопасным.

Сигналы и слоты между потоками

Qt поддерживает следующие типы подключений сигнал-слот:

  • Автоматическое подключение (по умолчанию) Если сигнал излучается в том потоке, к которому относится принимающий объект, то поведение аналогично прямому подключению. В противном случае поведение аналогично подключению в очереди."
  • Прямое подключение Слот вызывается немедленно при излучении сигнала. Слот выполняется в потоке отправителя, который не обязательно является потоком получателя.
  • Подключение в очереди Слот вызывается при возвращении управления к очереди событий потока получателя. Слот выполняется в потоке получателя.
  • Блокирующее подключение в очереди Слот вызывается как и для подключения в очереди, за исключением того, что текущий поток блокируется до возврата слота.

    Примечание: Использование этого типа для подключения объектов в одном потоке приведет к тупику.

  • Уникальное подключение Поведение аналогично автоматическому подключению, но подключение выполняется только в том случае, если оно не дублирует существующее подключение. т.е., если один и тот же сигнал уже подключен к одному и тому же слоту для одной и той же пары объектов, то подключение не выполняется и connect() возвращает false.

Тип подключения можно указать, передав дополнительный аргумент в connect(). Имейте в виду, что использование прямых подключений, когда отправитель и получатель находятся в разных потоках, небезопасно, если в потоке получателя запущена очередь событий, по той же причине, что и вызов любой функции объекта, находящегося в другом потоке, небезопасен.

QObject::connect() само по себе является потокобезопасным.

Пример Mandelbrot использует подключение в очереди для связи между рабочим потоком и главным потоком. Чтобы избежать зависания очереди событий главного потока (и, как следствие, пользовательского интерфейса приложения), все вычисления фрактала Мандельброта выполняются в отдельном рабочем потоке. Поток излучает сигнал при завершении отрисовки фрактала.

Аналогично, Пример блокирующего клиента Fortune использует отдельный поток для асинхронной связи с TCP-сервером.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/threads-qobject.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API