在Qt程序中如何优雅地实现线程切换

程序开发过程中,我们避免不了使用线程来执行耗时的操作,最常见的场景是启动线程执行耗时操作同时显示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; };

  1. workerOf方法可以认为是一个builder,它使用fun函数创建了一个简单的工作。
  2. concat方法用于连接多个顺序执行的worker。如果直接连接多个worker实际意义不大,但是在连接的多个worker间发生线程切换的话,它就很重要了。
  3. workOnSubThread方法可以切换线程,配合concat方法可以使多个worker执行在不同的线程。
  4. workOnMainThread方法指定worker切换到主线程执行。
  5. endWork方法表示worker执行结束后释放自己。
下面的代码简单测试了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...

    推荐阅读