关于qt:在Qt程序中如何优雅地实现线程切换

11次阅读

共计 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;
};
  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() << "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…

正文完
 0