Invoke slot method without connection?
Asked Answered
S

4

29

I have a live object implemented in the following way. It is used to execute long tasks in background. The main thread invoke the tasks by sending a signal to the public slots (i.e. doTask). Here is a stripped down example (not tested).

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 );
}

This (should) work as expected as long as doTask() is invoked by a signal. But if the main thread calls doTask() directly it will then be executed by the main thread. For some tasks, I want to enforce an execution by the live object's thread, even if the slot method is called directly.

I could add code in front of doTask() to check if the current thread is m_thread in which case it executes the method. If not I would like that doTask() emits a signal to 'this' so that an invocation of doTask() is queued in the m_thread exec loop and executed by it as soon as possible.

How could I do that ?

EDIT: Based on the proposed answer, here is the new code. The doTask method now delegates execution by the live objet's thread, even if called directly by the main thread. Called by signal still works as expected.

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 );
}

This is the most simple implementation I could find to support asynchronous method executions in a separate thread. The invocations of the doTask() methods will be queued and processed as soon as the thread is started. When called from the object thread, it will be executed immediately (not queued).

Note that the started() signal is emitted only when the thread is started. This means that doTask() method invocation queued before the thread is started will execute before the started() method slot is invoked. This is the reason I removed it from the initial implementation. Object initialization should thus preferably be performed in the constructor.

Schumacher answered 21/7, 2010 at 8:30 Comment(2)
I see a problem here: the docs say that you cannot move an object to another thread if it has a parent. Does your code work if MyTask is created with a parent?Embalm
A similar question was also already answered in QT + How to call slot from custom C++ code running in a different thread.Lasandralasater
M
33

You want to call QMetaObject::invokeMethod to do this. In your case, it would look something like

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 ) );
Madea answered 21/7, 2010 at 16:53 Comment(3)
This is awesome. It works like a charm. See my Edit for used code.Schumacher
Is it possible to do this without using string name for method?Acrosstheboard
@TomášZato yes. In Qt6 QMetaObject::invokeMethod(task, &Task::doTask, param); and in Qt5 QMetaObject::invokeMethod(task, []{task->doTask(param);});Astragal
Y
6

About the only improvement I'd add is to save some time on looking up the method:

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 ) );
}
Yukyukaghir answered 31/12, 2011 at 0:1 Comment(2)
How much do you gain by this extra effort?Louisville
The signature of the method passed to indexOfMethod should include arguments and be in normalized form (link) so the correct code should be m_doTaskImplIndex(metaObject()->indexOfMethod(metaObject()->normalizedSignature("doTaskImpl(int)")))Conchiferous
T
1

I suspect there is a bug in MyTask. If I've understood Qt internals correctly then

moveToThread(&m_thread);

will fail if parent isn't 0.

Threnode answered 14/6, 2012 at 13:36 Comment(0)
P
0

So, how about wrapping it all into a nice class?
I have also added a slot finishPlease, which will be added as last element in the message todo list, and gives feedback to the main program when it has actually processed all pending messages before it can be killed.

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) { }

};
Projectile answered 30/5, 2012 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.