1 背景
Qt界面程序是基于消息的,如果在界面程序中调用耗时操作会导致界面无反应。解决这种问题最容易想到的是将耗时操作放入线程中。Qt提供了QThread线程类,可以从Thread派生一个类实现虚函数run即可。其实QThread还有一种用法,不用从QThread派生,直接使用QThread就可以了。本文讲的就是这种方式。
2 实现
QThread的这种使用方式利用QObject的moveToThread方法。
首先定义一个Worker类,根据驱动器名称弹出磁盘。然后定义一Control类型将Worker的耗时调用放入线程中。
2.1 Worker定义
class Worker : public QObject
{
Q_OBJECT
public:
Worker() = default;
public slots:
void ejectDisk(QString const& dirverPath);
signals:
void finished(const QString &result);
};
如上所示方法ejectDisk根据驱动器名称弹出磁盘,如果磁盘在占用的情况下,这个方法会花费很长时间。方法执行结果通过信号finished返回。
2.2 Worker实现
void Worker::ejectDisk(QString const& dirverPath)
{
long dirverType(GetDriverType(dirverPath));
long diskNumber(GetDiskNumber(dirverPath));
if(dirverType != DRIVE_REMOVABLE)
{
emit finished("Disk is not removeable!");
return;
}
bool isOK = RemoveDisk(diskNumber, dirverType);
if(isOK)
emit finished("Eject is ok!");
else
emit finished("Eject is fail!");
}
函数流程:
- 获取驱动器类型
- 获取磁盘号
- 如果驱动器类型不是可移动的,直接返回
- 根据驱动器类型和磁盘号删除磁盘
- 成功返回"Eject is ok!"
- 失败返回"Eject is fail!"
注:GetDriverType,GetDiskNumber和RemoveDisk参考以前写的博客Qt弹出U盘及光盘。
2.3 Control定义
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QObject* parent);
~Controller();
void ejectDisk(QString const& dirverPath) { emit onEjectDisk(dirverPath); }
signals:
void finished(const QString &result);
void onEjectDisk(QString const& dirverPath);
private:
QThread workerThread;
};
该类定义两个信号量:
- finished是ejectDisk调用完成后的通知信号量
- onEjectDisk是启动ejectDisk的信号。
2.4 Control实现
Controller::Controller(QObject *parent)
: QObject(parent)
{
Worker* worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::onEjectDisk, worker, &Worker::ejectDisk);
connect(worker, &Worker::finished, this, &Controller::finished);
workerThread.start();
}
Controller::~DiskManagerController()
{
workerThread.quit();
workerThread.wait();
}
构造函数流程:
- 创建Worker对象worker
- 通过moveToThread方法将worker移到workerThread线程中。
- 连接workerThread的finished信号到worker的deleteLater方法,在线程退出时删除worker对象。
- 连接Controller对象信号onEjectDisk到worker的ejectDisk方法上,通过发送信号onEjectDisk来调用ejectDisk。
- 连接worker的finished信号到Controller对象的finished信号,将worker的finished信号传递到Controller对象
- 最后启动线程。
析构函数流程: - 退出线程
- 等待线程退出。
3 使用
从QWidget派生一个类型Widget.
3.1 Widget 定义
Widget 定义如下:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void customContextMenu(QPoint const& point);
private:
Ui::Widget *ui;
Controller* control;
};
3.2 Widget 实现
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, control(new Controller(this))
{
ui->setupUi(this);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(customContextMenu(QPoint)));
connect(control, &Controller::finished, this,
[=](QString const& result){
ui->labelTip->setText(result);
});
}
Widget::~Widget()
{
delete ui;
}
void Widget::customContextMenu(QPoint const&)
{
QMenu contextMenu;
contextMenu.addAction(tr("Eject"), this, [&](){
control->ejectDisk("f:");
});
contextMenu.exec(QCursor::pos());
}
如上所示:
- 单击显示右键菜单,在菜单中选择Eject后弹出磁盘。
- 弹出磁盘调用结束后结果显示在ui->labelTip控件的文本中。
4 总结
通过定义一个Control类将worker类的ejectDisk方法放入线程中,实现同步调用变为异步调用,调用结果通过信号返回。利用上述方法可以将多个函数调用放入线程中,不过多个函数同时调用,后调用会等待前面调用的结束后再调用。