C++多线程(1)

您所在的位置:网站首页 qt中的进度条设置控件 C++多线程(1)

C++多线程(1)

2023-07-24 08:59| 来源: 网络整理| 查看: 265

在编写界面程序时,通常会运行一些代码执行比较耗时的任务。如果想在执行任务的过程中向用户提示当前处理进度,通常会在界面上增加一个进度条。如果这个任务运行在主程序的线程中,就会造成主界面的卡死,进度条根本无法实时更新,直到任务执行完界面才能恢复。这个时候就应该利用多线程技术,将耗时任务的代码放到一个新的线程中运行,同时向主程序线程更新进度。

接下来我用Qt中QThread来实现这个目的。

我用的编程环境是Visual Studio 2019 Community和Qt 5.12。

主界面

在这里我设计一个按钮来启动一个耗时任务,并在主界面上实时刷新进度条以显示任务运行的进度。

首先,创建一个带界面的Qt桌面程序工程,选【Qt Widgets Application】。我将工程重新命名为QtProgressTest。用Qt Designer打开.ui文件,在主界面上增加一个Push Button和Progress Bar控件,另外增加一个Label控件以显示一些提示文字。如下图。

 接下来添加这个PushButton按钮的响应函数。在主程序增加一个void doSomething()槽(slot)函数,并在主类的构造函数中用connect语句将按钮的click信号与之相连。这个doSomething函数用于启动耗时任务。

connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(doSomething()));

再增加一个槽函数void updateProgress(int p, const char* msg);用于刷新进度条。

void QtProgressTest::updateProgress(int p, const char* msg) { ui.progressBar->setValue(p); ui.label->setText(msg); } 创建新线程并更新进度条

接下来,在doSomething函数中想办法启动一个新线程用于执行耗时任务,并用updateProgress槽函数接收新线程的进度信号。

QThread多线程可以用两种方法。第一种是继承QThread类,将耗时任务代码编写在run函数中。第二种是继承QObject类,执行耗时任务,并用moveToThread函数。在这里我将两种方法都实现一下。

第一种方法 继承QThread

基本思路是继承QThread,重写run函数

创建QThread的派生类MyThread,增加一个成员变量,是主程序类对象的指针

QtProgressTest* m_thread_creator;

这样做的目的是为了在主程序中创建MyThread类对象时,可以传入一些有用的参数。

所以MyThread的构造函数写成这样

MyThread::MyThread(QtProgressThread* creator, QObject* parent) : QThread(parent) { m_thread_creator = creator; }

增加一个信号,与以上槽函数updateProgress参数和返回值保持一致

signals: void progress(int p, const char* msg);

重写run函数

void MyThread::run() { startWork(); }

这个startWork函数就是一个耗时任务了,如下。

void MyThread::startWork() { for (size_t i = 1; i start(); }

编译并运行,效果如下。

以上就是QThread多线程进度条的简单实现。

回调函数如何更新进度条

在实际工程中,耗时任务并不是这么简单地可以在处理过程中利用emit语句发送信号,通常情况下耗时任务是底层的代码,并没有依赖Qt库,而是通过回调函数向外部抛出任务运行进度。

这种情况如何利用Qt实现多线程更新进度条呢?实际上可以新建一个QObject的派生类,在类函数中调用耗时任务的代码,然后再emit信号,相当于用Qt将底层代码包了一层。

创建QObject的派生类MyQtWorker,声明一个成员函数,作为回调函数,注意是静态的函数

static void progressCallback(int p, const char* msg);

 声明一个静态成员变量,是类自身的指针

static MyQtWorker* this_worker;

 在类的cpp文件中给这个变量赋值

MyQtWorker* MyQtWorker::this_worker = nullptr;

在类h文件中声明一个信号

signals: void progress(int p, const char* msg);

回调函数的实现如下,在这个回调函数中emit信号

void MyQtWorker::progressCallback(int p, const char* msg) { if(this_worker) emit this_worker->progress(p, msg); }

还需要在类的构造函数中给刚才的静态变量赋值,就是把类自身的指针赋给她

MyQtWorker::MyQtWorker() { this_worker = this; }

接下来模拟一个耗时任务的类TimeComsumingWork代码(不依赖Qt)

h文件

typedef void(* CallbackFun)(int, const char*); class TimeComsumingWork { public: void startWork(CallbackFun callback=nullptr); };

cpp文件

#include "TimeComsumingWork.h" #include "stdlib.h" void TimeComsumingWork::startWork(CallbackFun callback) { for (size_t i = 0; i < 100; i++) { _sleep(10); if (callback != nullptr) callback(i, "Working..."); } if (callback != nullptr) callback(100, "Done!"); }

细心的你应该能发现,这个TimeComsumingWork的startWork函数其实就是把上面MyThread中run的内容抄了一遍。

接下来,就要让MyQtWork(依赖Qt)来调用TimeComsumingWork(不依赖Qt)的代码,并通过回调函数来发送信号。

给MyQtWork添加一个槽函数doMyJob,调用耗时任务代码并将MyQtWork的成员函数progressCallback作为回调传入startWork函数的参数中

void MyQtWorker::doMyJob() { TimeComsumingWork w; w.startWork(progressCallback); }

以上实现了MyQtWork对底层耗时任务代码的封装,可向外emit信号,接下来就应该回到MyThread中了,调用MyQtWork的doMyJob函数,并在主界面上订阅MyQtWork发送的进度信号。

在MyThread中增加一个成员函数,注意,最开始定义的m_thread_creator在这儿起到作用了。

void MyThread::startMyWork() { MyQtWorker w; // 跨线程连接信号 connect(&w, &MyQtWorker::progress, m_thread_creator, &QtProgressTest::updateProgress); w.doMyJob(); }

最后一步,在MyThread的run函数调用startMyWork即可。

void MyThread::run() { startMyWork(); }

运行效果图略。

第二种方法 用moveToThread

基本思路是继承QObject类,将派生类的操作移动到新线程中。

前文中,已经实现了一个MyQtWork类,在这里正好可以利用起来。

对MyQtWork进行小小的改造,增加一个私有的成员变量QThread m_thread; 避免new delete的麻烦,这里我没有用指针。

给MyQtWork增加一个信号void done(); 在处理完成后emit这个信号。再给MyQtWork增加一个公有函数,用于启动线程。

void MyQtWorker::start() { m_thread.start(); }

将MyQtWorker的构造函数改造为:

MyQtWorker::MyQtWorker() { this_worker = this; this->moveToThread(&m_thread); connect(&m_thread, &QThread::started, this, &MyQtWorker::doMyJob); connect(this, &MyQtWorker::done, &m_thread, &QThread::quit); }

注意,这里先调用moveToThread,将MyQtWorker的代码移动到新线程中,并订阅两个信号,意思是当m_thread启动时,就执行耗时任务,当任务结束时,线程就退出。当外部调用MyQtWorker::start(),m_thread启动并触发doMyJob函数。

最后一步,在主程序中声明一个成员变量MyQtWorker* worker 并在构造函数中new

worker = new MyQtWorker;同时别忘了在析构中delete worker

同时connect一下MyQtWorker的信号

connect(worker, &MyQtWorker::progress, this, &QtProgressTest::updateProgress);

大功告成!

这样在MyQtWork中管理一个QThread对象,对外部调用者来说非常方便,不用再管理一个QThread指针的new和delete【这里参考了一篇文章的做法,见末尾】。很多文章在主程序中临时变量new一个QThread对象指针,并用到了QThread::deleteLater来自动释放new出来的QThread指针,那么在这种情况下就不要再手动delete了,程序会崩溃的。我一般的习惯是new和delete配对使用,所以就不采用这种方法了。

文中的代码已开源,下载地址:GitHub - CharlieV5/Qt: Qt example

参考:

Qt使用多线程的一些心得——2.继承QObject的多线程使用方法_尘中远的程序开发记录-CSDN博客_qobject线程

Qt新建线程的方法_hai200501019的专栏-CSDN博客



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3