Взаимодействие с главным потоком

Взаимодействие с главным потоком

При запуске приложения Qt работает только один поток — главный. Только этот поток может создать объект QApplication или QCoreApplication и вызвать для него функцию exec(). После вызова exec() этот поток либо ожидает возникновения какого-нибудь события, либо обрабатывает какое-нибудь событие.

Главный поток может запускать новые потоки, создавая объекты подкласса QThread, как мы это делали в предыдущем разделе. Если эти новые потоки должны взаимодействовать друг с другом, они могут совместно использовать переменные под управлением мьютексов, блокировок чтения/записи, семафоров или специальных событий. Но ни один из этих методов нельзя использовать для связи с главным потоком, поскольку они будут блокировать цикл обработки событий и «заморозят» интерфейс пользователя.

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

Однако когда вы связываете объекты, «живущие» в других потоках, механизм взаимодействия сигналов и слотов становится асинхронным. (Такое поведение можно изменить с помощью пятого параметра функции QObject::connect().) Внутри эти связи реализуются путем регистрации события. Слот затем вызывается в цикле обработки событий потока, в котором находится объект получателя. По умолчанию объект QObject существует в потоке, в котором он был создан; в любой момент можно изменить расположение объекта с помощью вызова функции QObject::moveToThread().

Рис. 18.3. Приложение Image Pro.

Для иллюстрации работы соединений сигнал—слот с разными потоками мы рассмотрим программный код приложения Image Pro — процессора изображений, обеспечивающего базовые возможности и позволяющего пользователю поворачивать, изменять размер и цвет изображения. В данном приложении используется один вторичный поток для выполнения операций над изображениями без блокировки цикла обработки событий. Это имеет существенное значение при обработке изображений очень большого размера. Вторичный поток имеет список выполняемых задач или «транзакций», и он генерирует события для главного окна, чтобы сообщать о том, как идет процесс их выполнения.

01 ImageWindow::ImageWindow()

02 {

03 imageLabel = new QLabel;

04 imageLabel->setBackgroundRole(QPalette::Dark);

05 imageLabel->setAutoFillBackground(true);

06 imageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);

07 setCentralWidget(imageLabel);

08 createActions();

09 createMenus();

10 statusBar()->showMessage(tr("Ready"), 2000);

11 connect(&thread, SIGNAL(transactionStarted(const QString &)),

12 statusBar(), SLOT(showMessage(const QString &)));

13 connect(&thread, SIGNAL(finished()),

14 this, SLOT(allTransactionsDone()));

15 setCurrentFile("");

16 }

Интересной частью конструктора ImageWindow являются два соединения сигнал—слот. В обоих случаях сигнал генерируется объектом TransactionThread, который мы вскоре рассмотрим.

01 void ImageWindow::flipHorizontally()

02 {

03 addTransaction(new FlipTransaction(Qt::Horizontal));

04 }

Слот flipHorizontally() создает транзакцию зеркального отражения и регистрирует ее при помощи закрытой функции addTransaction(). Функции flipVertically(), resizeImage(), convertTo32Bit(), convertTo8Bit() и convertTo1Bit() реализуются аналогично.

01 void ImageWindow::addTransaction(Transaction *transact)

02 {

03 thread.addTransaction(transact);

04 openAction->setEnabled(false);

05 saveAction->setEnabled(false);

06 saveAsAction->setEnabled(false);

07 }

Функция addTransaction() добавляет транзакцию в очередь транзакций вторичного потока и отключает команды Open, Save и Save As на время обработки транзакций.

01 void ImageWindow::allTransactionsDone()

02 {

03 openAction->setEnabled(true);

04 saveAction->setEnabled(true);

05 saveAsAction->setEnabled(true);

06 imageLabel->setPixmap(QPixmap::fromImage(thread.image()));

07 setWindowModified(true);

08 statusBar()->showMessage(tr("Ready"), 2000);

09 }

Слот allTransactionsDone() вызывается, когда очередь транзакций TransactionThread становится пустой.

Теперь давайте рассмотрим класс TransactionThread:

01 class TransactionThread : public QThread

02 {

03 Q_OBJECT

04 public:

05 void addTransaction(Transaction *transact);

06 void setImage(const QImage &image);

07 QImage image();

08 signals:

09 void transactionStarted(const QString &message);

10 protected:

11 void run();

12 private:

13 QMutex mutex;

14 QImage currentImage;

15 QQueue<Transaction *> transactions;

16 };

Класс TransactionThread содержит список обрабатываемых транзакций, которые выполняются по очереди в фоновом режиме.

01 void TransactionThread::addTransaction(Transaction *transact)

02 {

03 QMutexLocker locker(&mutex);

04 transactions.enqueue(transact);

05 if (!isRunning())

06 start();

07 }

Функция addTransaction() добавляет транзакцию в очередь транзакций и запускает поток транзакции, если он еще не выполняется. Доступ к переменной—члену transactions защищается мьютексом, потому что главный поток мог бы ее модифицировать функцией addTransaction() во время прохода по транзакциям transactions вторичного потока.

01 void TransactionThread::setImage(const QImage &image)

02 {

03 QMutexLocker locker(&mutex);

04 currentImage = image;

05 }

06 QImage TransactionThread::image()

07 {

08 QMutexLocker locker(&mutex);

09 return currentImage;

10 }

Функции setImage() и image() позволяют главному потоку установить изображение, для которого будут выполняться транзакции, и получить обработанное изображение после завершения всех транзакций. И вновь мы защищаем доступ к переменной—члену при помощи мьютекса.

01 void TransactionThread::run()

02 {

03 Transaction *transact;

04 forever {

05 mutex.lock();

06 if (transactions.isEmpty()) {

07 mutex.unlock();

08 break;

09 }

10 QImage oldImage = currentImage;

11 transact = transactions.dequeue();

12 mutex.unlock();

13 emit transactionStarted(transact->message());

14 QImage newImage = transact->apply(oldImage);

15 delete transact;

16 mutex.lock();

17 currentImage = newImage;

18 mutex.unlock();

19 }

20 }

Функция run() просматривает очередь транзакций и по очереди выполняет все транзакции путем вызова для них функции apply().

После старта транзакции мы генерируем сигнал transactionStarted() с сообщением, выводимым в строке состояния приложения. Когда обработка всех транзакций завершается, функция run() возвращает управление и QThread генерирует сигнал finished().

01 class Transaction

02 {

03 public:

04 virtual ~Transaction() { }

05 virtual QImage apply(const QImage &image) = 0;

06 virtual QString message() = 0;

07 };

Класс Transaction является абстрактным базовым классом, предназначенным для определения операций, которые пользователь может выполнять с изображением. Виртуальный деструктор необходим, потому что нам приходится удалять экземпляры подклассов Transaction через указатель transaction. (Кроме того, если мы его не предусмотрим, некоторые компиляторы выдадут предупреждение.) Transaction имеет три конкретных подкласса: FlipTransaction, ResizeTransaction и ConvertDepthTransaction. Нами будет рассмотрен только подкласс FlipTransaction; другие два подкласса имеют аналогичное определение.

01 class FlipTransaction : public Transaction

02 {

03 public:

04 FlipTransaction(Qt::Orientation orientation);

05 QImage apply(const QImage &image);

06 QString message();

07 private:

08 Qt::Orientation orientation;

09 };

Конструктор FlipTransaction принимает один параметр, который задает ориентацию зеркального отражения (по горизонтали или по вертикали).

01 QImage FlipTransaction::apply(const QImage &image)

02 {

03 return image.mirrored(

04 orientation == Qt::Horizontal, orientation == Qt::Vertical);

05 }

Функция apply() вызывает QImage::mirrored() для объекта QImage, полученного в виде параметра, и возвращает сформированный объект QImage.

01 QString FlipTransaction::message()

02 {

03 if (orientation == Qt::Horizontal) {

04 return QObject::tr("Flipping image horizontally...");

05 } else {

06 return QObject::tr("Flipping image vertically...");

07 }

08 }

Функция messageStr() возвращает сообщение, отображаемое в строке состояния в ходе выполнения операции. Данная функция вызывается из функции transactionThread::run(), кoгдa гeнepиpyeтcя cигнaл transactionStarted().