共计 4778 个字符,预计需要花费 12 分钟才能阅读完成。
程序开发过程中,咱们防止不了应用线程来执行耗时的操作,最常见的场景是启动线程执行耗时操作同时显示 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<void()> fun, QString name = "Worker");
Worker(QObject *, QString name = "Worker");
~Worker();
public:
virtual void doWork(std::function<void()> completeFun = nullptr);
}
在创立 Worker 的时候用户须要传入一个 lambda 函数,这个函数会在 doWork 办法中调用。咱们看下 doWork 办法的实现:
void Worker::doWork(std::function<void()> 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<void()> completeFun = nullptr ) override;
signals:
void run();
private:
std::shared_ptr<Runnable> _runnable;
};
ThreadWorker 类结构时须要两个参数。第一个参数是一个 Worker,ThreadWorker 的执行就是调用这个 Worker 的 doWork 办法。第二个参数是 QThread,ThreadWorker 将在这个线程中调用 worker 的 doWork 办法来实现线程的切换。咱们看下 ThreadWorker 如何重写的 doWork 办法。
void ThreadWorker::doWork(std::function<void()> completeFun )
{tracker( "ThreadWorker", "doWork");
_completeRunnable = std::make_shared<CallbackRunnable>(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<Runnable>( this->_name + "_Runnable", parentWorker);
_runnable->moveToThread(thread);
connect(this, &ThreadWorker::run, _runnable.get(), &Runnable::realRun );
}
}
在 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<void()> fun, QString name = "Worker" );
Worker(QObject*, QString name = "Worker");
~Worker();
Worker* findRootWorker();
public:
static Worker* workerOf(std::function<void()> fun, QObject* = nullptr, QString name = "workerOf" );
virtual void doWork(std::function<void()> completeFun = nullptr );
Worker* concat(std::function<void()> );
Worker* concat(Worker*);
Worker* workOnSubThread(QThread* = nullptr);
Worker* workOnMainThread();
Worker* endWork();
signals:
protected:
QString _name;
std::function<void()> _fun = nullptr;
std::shared_ptr<CallbackRunnable> _completeRunnable = nullptr;
};
- workerOf 办法能够认为是一个 builder,它应用 fun 函数创立了一个简略的工作。
- concat 办法用于连贯多个程序执行的 worker。如果间接连贯多个 worker 实际意义不大,然而在连贯的多个 worker 间产生线程切换的话,它就很重要了。
- workOnSubThread 办法能够切换线程,配合 concat 办法能够使多个 worker 执行在不同的线程。
- workOnMainThread 办法指定 worker 切换到主线程执行。
- 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() << "worker1 concat 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…