C++ GUI QT 第4版 之线程(三) 与主线程通讯

Qt 程序开始执行时,唯一的一个线程 —— 主线程 (main thread)也开始执行。主线程是唯一的,因为只有它才能创建 QApplication 或者是 QCoreApplication 对象,只有它才能通过应用程序对象调用 exec( ) 函数,只有它才能在 exec( ) 执行完毕后等待并处理事件。

 

主线程可以通过创建 QThread 子类对象开启一个新的线程,如果这些线程间需要相互通讯,它们可以使用共享变量,同时使用 mutexes,read-write locks,semaphores 或者 wait conditions 一些方法保持共享变量访问的同步性。但是由于这些技术可能锁定 event loop,同时还会冻结用户界面,所以其中没有一个能完成与主线程之间的通讯。

 

完成第二线程(secondary thread)与主线程之间的通讯的方法是:跨线程间的 signal-slot 连接。signal 一旦发出,其对应的 slot 函数便立即执行,这种连接是一种同步的机制。

 

但是当我们将不同线程中的对象连接在一起时,这种 signal-slot 通讯机制变得“不同步”(asynchronous)。signal-slot 机制的底层实现是传递一个 event,然后 slot 由 receiver 对象所在的线程中的 event loop 调用。默认情况下,一个 QObject 对象存在于创建它的线程中,但是任何时刻,调用 QObject : : moveToThread( ) 函数可以改变这种关系。

 

下面的示例用于演示如何进行跨线程间的 signal-slot 通讯。程序是一个基本的图像处理应用,可以对某个图像进行旋转、缩放等编辑:

 

 

这个程序中,使用了一个第二线程,专门处理图像,但是它并不锁定 event loop。这个第二线程有很多的任务需要完成,同时还需要向主窗口发送 events 来报告工作进度。

 

ImageWindow::ImageWindow()
{
    imageLabel = new QLabel;
    imageLabel->setBackgroundRole(QPalette::Dark);
    imageLabel->setAutoFillBackground(true);
    imageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    setCentralWidget(imageLabel);

    createActions();
    createMenus();

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

    connect(&thread, SIGNAL(transactionStarted(const QString &)),
            statusBar(), SLOT(showMessage(const QString &)));
    connect(&thread, SIGNAL(allTransactionsDone()),
            this, SLOT(allTransactionsDone()));

    setCurrentFile("");
}

 

在构造函数的部分,有一个 TransactionThread 对象,它是 QThread 的派生类,在这个应用程序中作为第二线程。在两个 connect( ) 中,都是这个线程对象向主线程(主窗口)发送信号。

 

程序中,进行图像处理的函数包括:水平翻转、垂直翻转、缩放图像、图像转换成 32 位,8位和 1 位等。这些函数都需要调用一个私有函数 addTransaction 将某个处理动作进行注册。例如下面的水平翻转函数:

 

void ImageWindow::flipHorizontally()
{
    addTransaction(new FlipTransaction(Qt::Horizontal));
}

 

那么这个私有函数实现如下:

 

void ImageWindow::addTransaction(Transaction *transact)
{
    thread.addTransaction(transact);
    openAction->setEnabled(false);
    saveAction->setEnabled(false);
    saveAsAction->setEnabled(false);
}

 

这个函数将一个 transaction 添加到第二线程的 transaction 队列中,并在这些 transactions 正被处理时,将 Open,Save 和 Save As 这些菜单项禁用。

 

这里的 Transaction 类是一个抽象基类,它是所有处理图像的操作的抽象:

 

class Transaction
{
public:
    virtual ~Transaction() { }

    virtual QImage apply(const QImage &image) = 0;
    virtual QString message() = 0;
};

 

其中虚析构函数是必须的,因为后面需要通过 Transaction 的指针删除一个子类的实例。Transaction 目前有三个具体的子类:FlipTransaction,ResizeTransaction 和 ConvertDepthTransaction 。这里只示例 FlipTransaction:

 

class FlipTransaction : public Transaction
{
public:
    FlipTransaction(Qt::Orientation orientation);

    QImage apply(const QImage &image);
    QString message();

private:
    Qt::Orientation orientation;
};

 

FlipTransaction 类主要实现基类中的两个纯虚函数,同时具有一个 Qt : : Orientation 类型的成员变量。它是一个枚举类型,用于指示对象的方位:Qt : : Horizontal 和 Qt : : Vertical 两个值。

 

apply( ) 函数的实现调用了 QIamge : : mirrored( ) 函数,返回的是翻转之后 QImage 对象,但是原图像对象不会改变:

 

QImage FlipTransaction::apply(const QImage &image)
{
    return image.mirrored(orientation == Qt::Horizontal,
                          orientation == Qt::Vertical);
}

 

下面是 ResizeTransaction : : apply( ) 的实现:

 

returnimage.scaled(size,Qt::IgnoreAspectRatio,Qt::SmoothTransformation);

 

下面是 ConvertDepthTransaction : : apply( ) 的实现:

 

QImageConvertDepthTransaction::apply(constQImage&image)

{QImage::Formatformat;switch(depth){case1:format=QImage::Format_Mono;break;case8:format=QImage::Format_Indexed8;break;case24:default:format=QImage::Format_RGB32;}returnimage.convertToFormat(format);} 

 

message( ) 函数返回一个 message 用于在状态栏中显示图像的处理情况。这个函数将在 transactionStarted( ) 信号发送时,在 Transaction : : run( ) 函数中调用。其它的 transactions 的子类都有类似的 message 函数。

 

接下来看,allTransactionDone( ) 这个 slot 函数。

 

void ImageWindow::allTransactionsDone()
{
    openAction->setEnabled(true);
    saveAction->setEnabled(true);
    saveAsAction->setEnabled(true);
    imageLabel->setPixmap(QPixmap::fromImage(thread.image()));
    setWindowModified(true);
    statusBar()->showMessage(tr("Ready"), 2000);
}

 

当 TransactionThread 的 transaction 队列中空了的时候,这个函数被调用。 

 

 

下面是 TransactionThread 类:

 

 class TransactionThread : public QThread
{
    Q_OBJECT

public:
    TransactionThread();
    ~TransactionThread();

    void addTransaction(Transaction *transact);
    void setImage(const QImage &image);
    QImage image();

signals:
    void transactionStarted(const QString &message);
    void allTransactionsDone();

protected:
    void run();

private:
    QImage currentImage;
    QQueue<Transaction *> transactions;
    QWaitCondition transactionAdded;
    QMutex mutex;
};

 

在这个类中,run 函数在自己的线程中执行,其它的函数则从主线程调用。在这个类中,维护着一个 transaction 队列,其中的每个 transaction 将一个接一个地被执行。成员变量分别为:currentImage 保存着一个当前将被处理的图像对象;trarnsactions 是 transactions 队列;transactionAdded 是一个 QWaitCondition 类对象,当新的 transaction 已加入队列时,这个条件用于唤醒线程;mutex 保护 currentImage 和 transactions 成员变量,以防止多个线程同时访问。

 

TransactionThread::TransactionThread()
{
    start();
}

 

第二线程的构造函数中,调用 QThread : : start( ) 开启将要执行 transaction 的线程。在析构函数中,清空队列,将一个特殊的 EndTransaction 加入队列。唤醒线程,并使用 QThread : : wait( ) 等待线程结束。如果没有 wait( ),当其它的线程访问类中的成员变量时,程序有可能崩溃:

 

TransactionThread::~TransactionThread()
{
    {
        QMutexLocker locker(&mutex);
        while (!transactions.isEmpty())
            delete transactions.dequeue();
        transactions.enqueue(EndTransaction);
        transactionAdded.wakeOne();
    }

    wait();
}

 

在上面的代码中,大括号括起来的代码块中, QMutexLocker 的析构造函数将被调用。其中将 mutex 解锁,在 wait( ) 之前解锁这很重要,否则将引起死锁的可能性(第二线程一直等待 mutex 被解锁,而主线程一直等待第二线程完成而操持 mutex 不放)。

 

QWaitCondition : : wakeOne( ) 函数唤醒一个正在等待某个条件的线程。被唤醒的线程取决于操作系统的排程策略,并不能控制和提前预知哪个线程将被唤醒。如果需要唤醒某个指定的线程,通常需要使用不同的等待条件,使用不同的线程专门等待不同的等待条件。

 

addTransaction( ) 函数将一个 transaction 添加到队列中,并唤醒 transaction 线程。所有访问 transactions 的成员变量都由一个 mutex 保护,因为在第二线程遍历队列中的 transaction 时主线程可能修改这些变量。

 

void TransactionThread::addTransaction(Transaction *transact)
{
    QMutexLocker locker(&mutex);
    transactions.enqueue(transact);
    transactionAdded.wakeOne();
}

 

第二线程,TransactionThread,的 run( ) 函数遍历其中的 transaction 队列,并执行每个 transaction 中的 apply( ) 函数,直到遇到 EndTransaction 这个 transaction 为止。如果队列为空,线程将等待“transaction added”条件。每次在执行一个 transaction 之前,总是先发送 transactionStarted( ) 信号以显示在主窗口的状态栏中,当所有的 transactions 都执行完毕后,发送一个 allTransactionsDone( ) 信号。

 

void TransactionThread::run()
{
    Transaction *transact = 0;
    QImage oldImage;

    forever {
        {
            QMutexLocker locker(&mutex);

            if (transactions.isEmpty())
                transactionAdded.wait(&mutex);
            transact = transactions.dequeue();
            if (transact == EndTransaction)
                break;

            oldImage = currentImage;
        }

        emit transactionStarted(transact->message());
        QImage newImage = transact->apply(oldImage);
        delete transact;

        {
            QMutexLocker locker(&mutex);
            currentImage = newImage;
            if (transactions.isEmpty())
                emit allTransactionsDone();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值