【问题标题】:Execute slots inside a QThreadPool在 QThreadPool 中执行槽
【发布时间】:2013-07-26 09:04:18
【问题描述】:

我有一个应该在线程中运行的类,并且需要一个用于插槽的事件循环,目前我使用moveToThread() 很好地运行它,但我想使用QThreadPool,但我遇到了一个问题。

当使用QThreadPool 运行时,我的runnable 的run() 方法是从池线程中调用的(我使用QThread::currentThread() 进行了检查),但我的插槽没有在池线程中运行,所以我认为对象不会移动到池中的线程。

我认为这是因为我知道插槽是在接收器的线程中运行的,这正是我在使用 moveToThread() 方法和 QThread 时得到的(正确)行为。

如何让我的QRunnable(下例中的Foo)完全在池线程中运行? 还是我做错了什么或理解错了?

以下 POC 演示了该问题:

foo.h

#ifndef FOO_H
#define FOO_H

#include <QObject>
#include <QRunnable>
#include <QEventLoop>

class Foo : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit Foo(int data, QObject *parent = 0);
    void run();
signals:
    void startWorking();
public slots:
    void doWork();

private:
    QEventLoop eventLoop;
    int data;
};

#endif // FOO_H

foo.cpp

#include "foo.h"

#include <QThread>
#include <QDebug>

Foo::Foo(int d, QObject *parent) :
    QObject(parent), eventLoop(this), data(d)
{
}

void Foo::run()
{
    qDebug() << "run() in: " << QThread::currentThread();
    connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
    emit startWorking();
    eventLoop.exec();
}

void Foo::doWork()
{
    qDebug() << "doWork() in: " << QThread::currentThread();
}

ma​​in.cpp

#include <QCoreApplication>
#include <QThreadPool>

#include "foo.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Foo *foo = new Foo(42);

    QThreadPool::globalInstance()->start(foo);

    return a.exec();
}

但是请注意,在我的真实代码中,信号不会立即发出,因为它会在我收到网络上的一些数据之后发出。

PS:POC 也可以在here找到。

【问题讨论】:

    标签: c++ multithreading qt threadpool qeventloop


    【解决方案1】:

    也许您可以将class Foo 中的逻辑拆分为两部分:
    主机QRunnableQEventLoop 和一个工作线程QObject,您在调用QEventLoop::exec 方法之前在run() 的工作线程上创建它们。然后将所有信号转发给工作对象。 所以现在您的插槽将在池线程上调用。
    但是,QThreadPool 是为执行大量短任务而设计的,而不会创建太多并发线程。一些任务已排队等待其他任务完成。如果这不是您的意图,您可能想回到旧的 QThread 并改用它。

    【讨论】:

    • 有趣,我会试一试。
    • +1,标记为答案,因为我解决了问题,与您所说的不完全一样,因为我在工作程序中运行事件循环,而不是在可运行程序中。
    【解决方案2】:

    您可以同时支持这两种模式,但需要外部协调。我的策略是从QRunnable::run 内部发出一个信号,传递当前线程。当您计划在线程池中使用它时,请在此信号上使用 Qt::BlockingQueuedConnection 并在那里执行您的 moveToThread。否则,将其移至QThread 并发出信号以开始正常工作。

    TaskRunner.h

    #pragma once
    
    #include <QObject>
    #include <QRunnable>
    #include <QThread>
    
    class TaskRunner : public QObject, public QRunnable
    {
      Q_OBJECT
    public:
      TaskRunner(int data, QObject* parent = nullptr);
    
      void run() override;
    
    Q_SIGNALS:
    
      void start();
      void starting(QThread*);
      void stop();
    
    private:
      int data;
    };
    

    TaskRunner.cpp

    #include "TaskRunner.h"
    #include <QEventLoop>
    #include <stdexcept>
    
    
    TaskRunner::TaskRunner(int data, QObject* parent)
    : QObject(parent), data(data)
    {
        // start should call run in the associated thread
        QObject::connect(this, &TaskRunner::start, this, &TaskRunner::run);
    }
    
    void TaskRunner::run()
    {
        // in a thread pool, give a chance to move us to the current thread
        Q_EMIT starting(QThread::currentThread());
    
        if (thread() != QThread::currentThread())
          throw std::logic_error("Not associated with proper thread.");
    
        QEventLoop loop;
        QObject::connect(this, &TaskRunner::stop, &loop, &QEventLoop::quit);
        // other logic here perhaps
        loop.exec();
    }
    

    ma​​in.cpp

    #include <QCoreApplication>
    #include <QThreadPool>
    
    #include "TaskRunner.h"
    
    // comment to switch
    #define USE_QTHREAD
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        auto runner = new TaskRunner(42);
    
    #ifdef USE_QTHREAD
        // option 1: on a QThread
        auto thread = new QThread(&a);
        runner->moveToThread(thread);
        QObject::connect(thread, &QThread::finished, runner, &QObject::deleteLater);
        Q_EMIT runner->start();
    
        // stop condition not shown
    #else
        // option 2: in a thread pool
        QObject::connect(
          runner, &TaskRunner::starting, 
          runner, &QObject::moveToThread,
          Qt::BlockingQueuedConnection);
        QThreadPool::globalInstance()->start(runner);
    
        // stop condition not shown
    #endif
    
        return a.exec();
    }
    

    【讨论】:

      【解决方案3】:

      自从你的连接电话

      connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
      

      使用默认参数作为连接类型,它将是一个Qt::Autoconnection。 信号是从池化线程发出的,slot 仍然属于 foo,它与主线程有线程亲和性。自动连接会决定将 slot 放入主线程的事件队列中。

      有两种方法可以解决此问题:

      1.

      connect(this, SIGNAL(startWorking()), this, SLOT(doWork()), Qt::DirectConnection);
      

      并删除 eventloop.exec();

      2.

      在run方法中,在连接信号和槽之前将foo对象移动到当前线程。

      【讨论】:

      • 第二种解决方案不起作用,因为我没有对池中线程的引用,我不知道将分配哪个线程来工作。第一个也不起作用,因为信号不会立即发出,并且由于我没有事件循环,所以线程将在 run() 返回后退出。
      • 您不能将对象移动到当前线程,因为您只能从所有者线程“推送”。使用 Qt::DirectConnection 也是一个坏主意,因为这并不能保证将在工作线程上调用槽(正如 op 似乎期望的那样)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-30
      • 1970-01-01
      • 2012-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多