Qt线程另一种使用方式(QThread, moveToThread)

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方法放入线程中,实现同步调用变为异步调用,调用结果通过信号返回。利用上述方法可以将多个函数调用放入线程中,不过多个函数同时调用,后调用会等待前面调用的结束后再调用。

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flysnow010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值