Вызвать метод слота без подключения? - PullRequest
25 голосов
/ 21 июля 2010

У меня есть живой объект, реализованный следующим образом.Используется для выполнения длинных задач в фоновом режиме.Основной поток вызывает задачи, посылая сигнал в открытые слоты (т.е. 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 () был поставлен в очередь в цикле exec m_thread и выполнялся им как можно скорее.

Как я мог это сделать?

РЕДАКТИРОВАТЬ : На основе предложенного ответа, вот новый код.Метод 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 () будут поставлены в очередь и обработаны, как только поток будет запущен.При вызове из потока объекта он будет выполнен немедленно (не помещен в очередь).

Обратите внимание, что сигнал start () испускается только при запуске потока.Это означает, что вызов метода doTask (), поставленный в очередь до запуска потока, будет выполнен до того, как будет вызван слот метода start ().По этой причине я удалил его из первоначальной реализации.Таким образом, инициализация объекта должна предпочтительно выполняться в конструкторе.

Ответы [ 4 ]

28 голосов
/ 21 июля 2010

Вы хотите позвонить 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 ) );
5 голосов
/ 31 декабря 2011

О единственном улучшении, которое я бы добавил, это сэкономить некоторое время на поиске метода:

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 голос
/ 14 июня 2012

Я подозреваю, что в MyTask есть ошибка.Если я правильно понял внутренности Qt,

moveToThread(&m_thread);

завершится неудачей, если parent не равно 0.

0 голосов
/ 30 мая 2012

Итак, как насчет того, чтобы превратить все это в хороший класс?
Я также добавил слот 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) { }

};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...