在没有连接的情况下调用槽函数方法?

29

我有一个使用以下方式实现的活对象。它用于在后台执行长时间任务。主线程通过向公共槽(即doTask)发送信号来调用任务。这是一个简化的示例(未经测试)。

class MyTask : public QObject
{
    Q_OBJECT

public:
    MyTask();
    ~MyTask();

public slots:
    void doTask( int param );

private slots:
    void stated();

signals:
    void taskCompleted( int result );

private:
    QThread m_thread;
};


MyTask::MyTask()
{
   moveToThread(&m_thread);
   connect( &m_thread, SIGNAL(started()), this, SLOT(started()));
   m_thread.start();
}

MyTask::~MyTask()
{
    // Gracefull thread termination (queued in exec loop)
    if( m_thread.isRunning() )
    {
        m_thread.quit();
        m_thread.wait();
    }
}

void MyTask::started()
{
    // initialize live object
}

void MyTask::doTask( int param )
{
    sleep( 10 );
    emit taskCompleted( param*2 );
}
只要通过信号调用doTask(),就像预期的那样工作。但是如果主线程直接调用doTask(),那么它将由主线程执行。对于某些任务,我想强制在活动对象的线程中执行,即使直接调用槽方法。
我可以在doTask()前面添加代码,检查当前线程是否为m_thread,在这种情况下执行该方法。如果不是,我希望doTask()向'this'发出一个信号,以便将doTask()的调用排队在m_thread exec循环中,并尽快由其执行。
我该怎么做?
编辑:根据提议的答案,这是新代码。即使由主线程直接调用,doTask方法现在也会委托执行到活动对象的线程。通过信号调用仍然按预期工作。
class MyTask : public QObject
{
    Q_OBJECT

public:
    explicit MyTask( QObject *parent = 0 );
    ~MyTask();

public slots:
    void doTask( int param );

private slots:
    void doTaskImpl( int param );

signals:
    void taskCompleted( int result );

private:
    QThread m_thread;
};

MyTask::MyTask( QObject *parent) : QObject(parent)
{
   moveToThread(&m_thread);
   m_thread.start();
}

MyTask::~MyTask()
{
    // Gracefull thread termination (queued in exec loop)
    if( m_thread.isRunning() )
    {
        m_thread.quit();
        m_thread.wait();
    }
}

void MyTask::doTask( int param )
{
    QMetaObject::invokeMethod( this, "doTaskImpl", Q_ARG( int, param ) );
}

void MyTask::doTaskImpl( int param )
{
    // Do the live oject's asynchronous task
    sleep( 10 );
    emit taskCompleted( param*2 );
}
这是我能找到的最简单的实现来支持在单独的线程中执行异步方法。 doTask() 方法的调用将被排队并在线程启动时立即处理。当从对象线程调用时,它将立即执行(而不是排队)。
请注意,只有在线程启动时才会发出 started() 信号。这意味着在线程启动之前排队的 doTask() 方法调用将在调用 started() 方法槽之前执行。这就是我从最初的实现中删除它的原因。因此,最好在构造函数中执行对象初始化。

我在这里看到一个问题:文档说如果一个对象有父对象,那么你不能将其移动到另一个线程。如果MyTask是有父对象创建的,那么你的代码能运行吗? - andref
类似的问题在QT +如何从运行在不同线程中的自定义C ++代码调用插槽中已经得到了回答。 - Trilarion
4个回答

33

您想要调用QMetaObject::invokeMethod来实现此目的。在您的情况下,它应该类似于以下内容:

MyTask *task;
int param;
// ...
// Will automatically change threads, if needed, to execute 
// the equivalent of:
// (void)task->doTask( param );
QMetaObject::invokeMethod( task, "doTask", Q_ARG( int, param ) );

这太棒了。它像魔法一样运行。 请查看我的编辑以获取使用的代码。 - chmike
17
不使用方法名称的字符串,是否可以做到这一点? - Tomáš Zato

6

关于唯一的改进,我会节省查找方法的时间:

class MyTask {
// ...
private:
  int m_doTaskImplIndex;
};

MyTask::MyTask() :
  //...
  m_doTaskImplIndex(metaObject()->indexOfMethod("doTaskImpl"))
  //...
{}

void MyTask::doTask( int param )
{
  metaObject()->method(m_doTaskImplIndex).invoke(this, Q_ARG( int, param ) );
}

1
这份额外努力会给你带来多大的收益? - Johannes S.
传递给 indexOfMethod 方法的签名应包括参数并且应处于规范化形式 (链接),因此正确的代码应该是 m_doTaskImplIndex(metaObject()->indexOfMethod(metaObject()->normalizedSignature("doTaskImpl(int)"))) - Enrico Detoma

1
我怀疑MyTask中存在一个bug。如果我正确理解了Qt的内部机制,那么下面这行代码: moveToThread(&m_thread); 如果parent不是0的话,将会失败。

0
那么,把所有东西都封装到一个漂亮的类中怎么样?
我还添加了一个插槽finishPlease,它将被添加为消息待办列表中的最后一个元素,并在主程序处理完所有挂起的消息之前向主程序提供反馈,以便可以终止它。
class Threaded : public QObject
{
    Q_OBJECT
public:
    Threaded() {
        thread = new QThread(this);
        this->moveToThread(thread);
        connect(thread, SIGNAL(started()), this, SLOT(init()), \
                                                  Qt::QueuedConnection);
        thread->start();
    }

    virtual ~Threaded() {
        thread->exit();
        thread->wait();
        delete thread;
    }

signals:
    void okayKillMe();

public slots:
    virtual void init() = 0;
    void finishPlease() {emit okayKillMe();}

protected:
    QThread* thread;
};

class MyClass : public Threaded
{
  Q_OBJECT
public:
    MyClass() { }
    virtual ~MyClass() { }

public slots:
    void init() { }
    void doStuff() { }
    void doOtherStuff(int* data) { }

};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接