14-1 创建线程()

用Qt进行多线程编程非常简单:只需要从QThread类继承,并重写run()函数即可。为了说明线程是如何工作的,我们先看一段非常简单的QThread子类,这个类在控制台上重复打印一个给定字符。程序的人机界面如下图14.1所示:


代码如下:

class Thread : public QThread
{
    Q_OBJECT

public:
    Thread();

    void setMessage(const QString &message);
    void stop();

protected:
    void run();

private:
    QString messageStr;
    volatile bool stopped;
};


类Thread从QThread继承并重写了run()函数,并提供了两个额外的函数:setMessage()stop()

变量stopped声明为volatile,是因为它需要从不同的线程中获取,我们需要保证每次读取它的时候都是重新从它的地址读取。如果不使用volatile关键字,编辑器可能有优化这个变量的存取,导致错误的结果。

Thread::Thread()
{
    stopped = false;
}


在构造函数中,设置stopped变量为false。

void Thread::run()
{
    while (!stopped)
        std::cerr << qPrintable(messageStr);
    stopped = false;
    std::cerr << std::endl;
}


在开始执行线程是,函数Thread::run()就会被调用。只要变量stopped为false,函数就在控制台上打印给定的消息。当离开run()后,线程终止。

void Thread::stop()
{
    stopped = true;
}


函数stop()将变量stopped置为true,通知run()停止打印向控制台打印字符。在任何时候的任何线程中,这个函数都能够调用。在本例中,我们认为给一个bool量赋值是一个原子操作。因为一个bool量只有两个值,因此这个假设是合理的。在本章的后面部分,我们将会介绍如何使用QMutex来确保一个变量赋值是一个原子操作。

QThread 提供了terminate函数来终止正在执行的线程。但是并不推荐使用terminate()函数,因为terminate()函数能够在任何时间终止线程,线程却没有时间去做可能的清理工作。我们这里使用了stopped 变量和stop(),是更加安全的做法。

我们将会看到在一个简单的Qt应用中,如何使用Thread类,在主线程之外在增加两个线程A和B。

class ThreadDialog : public QDialog
{
    Q_OBJECT

public:
    ThreadDialog(QWidget *parent = 0);

protected:
    void closeEvent(QCloseEvent *event);

private slots:
    void startOrStopThreadA();
    void startOrStopThreadB();

private:
    Thread threadA;
    Thread threadB;
    QPushButton *threadAButton;
    QPushButton *threadBButton;
    QPushButton *quitButton;
};


ThreadDialog 中声明了两个Thread类型的线程,并提供了一个三个按钮的用户界面。

ThreadDialog::ThreadDialog(QWidget *parent)
    : QDialog(parent)
{
    threadA.setMessage("A");
    threadB.setMessage("B");

    threadAButton = new QPushButton(tr("Start A"));
    threadBButton = new QPushButton(tr("Start B"));
    quitButton = new QPushButton(tr("Quit"));
    quitButton->setDefault(true);

    connect(threadAButton, SIGNAL(clicked()),
            this, SLOT(startOrStopThreadA()));
    connect(threadBButton, SIGNAL(clicked()),
            this, SLOT(startOrStopThreadB()));
    ...
}


在构造函数中,调用setMessage使线程A打印“A”,而线程B打印“B”。

当用户点击线程A按钮,如果线程A在运行,则停止线程A,否则启动线程A。同时更新按钮上显示的文本。

函数startOrStopThreadB的实现与startOrStopThreadA类似。

void ThreadDialog::startOrStopThreadA()
{
    if (threadA.isRunning()) {
        threadA.stop();
        threadAButton->setText(tr("Start A"));
    } else {
        threadA.start();
        threadAButton->setText(tr("Stop A"));
    }
}
void ThreadDialog::startOrStopThreadB()
{
    if (threadB.isRunning()) {
        threadB.stop();
        threadBButton->setText(tr("Start B"));
    } else {
        threadB.start();
        threadBButton->setText(tr("Stop B"));
    }
}

void ThreadDialog::closeEvent(QCloseEvent *event)
{
    threadA.stop();
    threadB.stop();
    threadA.wait();
    threadB.wait();
    event->accept();
}


如果用户点击了退出按钮或者关闭了窗口,在接受消息event->accept()之前,我们停止运行的线程并等待线程完成(调用QThread::wait()),这样就保证了程序在干净的状态下退出,当然在本例中并没有什么影响。
如果你运行程序后点击StartA按钮,控制台将会打印“A”。如果点击StartB按钮,则控制台上轮流打印“A”和“B”。点击StopA后,控制台只会打印“B”。


你可能感兴趣的:(C++,GUI,Programming,with,Qt,4,多线程,qt)