程序开发过程中,我们避免不了使用线程来执行耗时的操作,最常见的场景是启动线程执行耗时操作同时显示loading画面,当耗时操作完成时关闭loading界面。这样简单的操作中也涉及到了线程切换的动作。首先显示loading画面代码需要执行在ui线程,然后耗时操作执行在子线程。显示loading画面-》切换子线程-》执行耗时操作-》切换ui线程-》关闭loading画面。
Qt中有一个重要的概念是信号和槽,使用信号和槽我们可以实现消息数据的传递,当然这种传递也包括线程间的消息数据传递。槽指的是槽函数,槽函数运行的线程由它所在的QObject绑定的线程决定的。我们可以通过调用moveToThread
方法 改变QObject对象绑定的线程。槽通过connect方法连接一个信号后,发射信号和槽函数执行可以分别在两个线程中,进而实现了线程的切换。
我们通过信号和槽可以实现线程的切换,但是在使用的时候还是不够灵活。比如程序中异步执行的操作肯定很多,如果为每个异步操作都定义一份自己的信号与槽,那么信号和槽的数量会特别的多,过多的信号和槽使得程序的执行流程过于混乱。
为了解决异步操作定义导致的信号与槽数量过多的问题,我们需要基于信号与槽的技术定义一个共同的处理流程,避免为异步操作定义定制化的信号与槽。
首先我将操作抽象成一个Worker类,Worker具体的执行内容由 Lambda 函数来指定。
class Worker : public QObject {
Q_OBJECT
protected:
Worker(QObject *, std::function fun, QString name = "Worker");
Worker(QObject *, QString name = "Worker");
~Worker();
public:virtual void doWork(std::function completeFun = nullptr);
}
在创建Worker的时候用户需要传入一个lambda函数,这个函数会在doWork方法中调用。我们看下doWork方法的实现:
void Worker::doWork( std::function completeFun )
{
tracker( "Worker", "doWork" );
if( _fun )
{
_fun();
}
if( completeFun )
{
completeFun();
}
}
doWork方法的实现比较简单,首先它执行了构造函数传入的lambda函数,然后调用doWork的lambda函数参数。这里的调用都是顺序执行的,没有发生线程的切换。为了支持线程切换,我创建了一个ThreadWorker类继承至Worker类。
class ThreadWorker : public Worker
{
Q_OBJECT
public:
ThreadWorker( Worker*, QThread* );
~ThreadWorker();
void doWork( std::function completeFun = nullptr ) override;
signals:
void run();
private:
std::shared_ptr _runnable;
};
ThreadWorker类构造时需要两个参数。第一个参数是一个Worker,ThreadWorker的执行就是调用这个Worker的doWork方法。第二个参数是QThread,ThreadWorker将在这个线程中调用worker的doWork方法来实现线程的切换。我们看下ThreadWorker如何重写的doWork方法。
void ThreadWorker::doWork( std::function completeFun )
{
tracker( "ThreadWorker", "doWork" );
_completeRunnable = std::make_shared( completeFun, _name + "_CallbackRunnable" );
connect( _runnable.get(), &Runnable::finished, _completeRunnable.get(), &CallbackRunnable::realRun );
emit run();
}
doWork方法中为completeFun 函数创建了一个CallbackRunnable,同时CallbackRunnable会连接到Runnable::finished信号。然后发射了一个run()信号。我们发现这里没有执行具体的工作,那么具体工作如何触发执行的呢。我们看下谁处理了run()信号。
ThreadWorker::ThreadWorker( Worker *parentWorker, QThread *outerThread )
: Worker( parentWorker, "ThreadWorker" )
{
tracker( "ThreadWorker", "ThreadWorker" );
auto thread = outerThread == nullptr ? ThreadManager::getSubThread() : outerThread;
if( thread )
{
_runnable = std::make_shared( this->_name + "_Runnable", parentWorker );
_runnable->moveToThread( thread );
connect( this, &ThreadWorker::run, _runnable.get(), &Runnable::realRun );
}
}
【在Qt程序中如何优雅地实现线程切换】在ThreadWorker的构造实现中我们看到了_runnable会处理ThreadWorker::run信号。这里有一点需要注意, _runnable对象被绑定到了子线程,这样_runnable的槽函数都会执行在这个线程了。我们看下Runnable::realRun的实现:
void Runnable::realRun()
{
tracker( "Runnable", "realRun _originalWorkder " << _originalWorkder );
if( _originalWorkder )
{
_originalWorkder->doWork( [=]() -> void { sendFinish();
} );
}
}
Runnable::realRun调用了worker的doWork方法,这样保证了worker的工作在子线程中执行的。为了保证ThreadWorker的completeFun函数在doWork方法执行的线程中执行,CallbackRunnable通过槽函数CallbackRunnable::realRun来接收Runnable::finished信号,将子线程的回调切换到ThreadWorker的doWork方法执行线程。
到这里我们把异步执行和线程切换的实现讲解完了,为了让异步执行和线程切换更灵活,我采用了链式调用的方式支持用户灵活切换线程。
class Worker : public QObject
{
Q_OBJECT
protected:
Worker( QObject*, std::function fun, QString name = "Worker" );
Worker( QObject*, QString name = "Worker" );
~Worker();
Worker* findRootWorker();
public:
static Worker* workerOf( std::function fun, QObject* = nullptr, QString name = "workerOf" );
virtual void doWork( std::function completeFun = nullptr );
Worker* concat( std::function );
Worker* concat( Worker* );
Worker* workOnSubThread( QThread* = nullptr );
Worker* workOnMainThread();
Worker* endWork();
signals:protected:
QString _name;
std::function _fun = nullptr;
std::shared_ptr _completeRunnable = nullptr;
};
- workerOf方法可以认为是一个builder,它使用fun函数创建了一个简单的工作。
- concat方法用于连接多个顺序执行的worker。如果直接连接多个worker实际意义不大,但是在连接的多个worker间发生线程切换的话,它就很重要了。
- workOnSubThread方法可以切换线程,配合concat方法可以使多个worker执行在不同的线程。
- workOnMainThread方法指定worker切换到主线程执行。
- endWork方法表示worker执行结束后释放自己。
Worker::workerOf([]() -> void {
//运行在线程subThread1
qDebug() << "worker1 start++++++ " << QThread::currentThread();
QThread::msleep(1000);
qDebug() << "worker1 end++++++ " << QThread::currentThread();
})
->workOnSubThread(&subThread1)
->concat([]() -> void {
//运行在线程subThread2
qDebug() << "worker1 concat work start++++++ "
<< QThread::currentThread();
QThread::msleep(1000);
qDebug() << "worker1concat work end++++++ "
<< QThread::currentThread();
})
->workOnSubThread(&subThread2)
//运行在ThreadManager::getSubThread
->concat(generateWorker()->endWork())
->workOnSubThread(ThreadManager::getSubThread())
->endWork()
->doWork([]() -> void {
//这个函数运行在调用doWork方法的线程
qDebug() << "worker1 complete thread id " << QThread::currentThread();
});
这里为了充分展示worker切换线程的灵活性,我通过workOnSubThread方法多次切换线程,注释中标注了各个部分执行在什么线程。
worker也是通过信号与槽实现的线程切换,但是我们不需要为每个异步操作都定义响应的信号与槽。worker内部封装了信号与槽通信的共通部分,同时它还通过lambda函数方式自定义了worker的具体工作内容,我们使用worker实现异步和线程切换更加灵活,并且不需要为新的异步工作定义新的类。下面提供了详细的代码,感兴趣的朋友可以下载下来运行。
git地址:https://github.com/mjlong1231...
原文链接:https://blog.csdn.net/mjlong1...