Invoke slot method without connection?

2019-02-03 03:51发布

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.

标签: qt4
4条回答
甜甜的少女心
2楼-- · 2019-02-03 04:15

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 ) );
}
查看更多
倾城 Initia
3楼-- · 2019-02-03 04:25

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 ) );
查看更多
别忘想泡老子
4楼-- · 2019-02-03 04:30

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.

查看更多
萌系小妹纸
5楼-- · 2019-02-03 04:31

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

};
查看更多
登录 后发表回答