Вызывать метод слота без подключения?
У меня есть живой объект, реализованный следующим образом. Он используется для выполнения длинных задач в фоновом режиме. Основной поток вызывает задачи путем отправки сигнала в общедоступные слоты (т.е. 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 теперь делегирует выполнение потоком live objet, даже если он вызван непосредственно основным потоком. Вызывается сигналом по-прежнему работает как ожидалось.
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 asynchronous task
sleep( 10 );
emit taskCompleted( param*2 );
}
Это самая простая реализация, которую я смог найти для поддержки выполнения асинхронных методов в отдельном потоке. Вызовы методов doTask() будут поставлены в очередь и обработаны, как только начнется поток. При вызове из потока объектов он будет выполнен немедленно (не в очереди).
Обратите внимание, что сигнал start() испускается только при запуске потока. Это означает, что вызов метода doTask(), поставленный перед запуском потока, будет выполняться до вызова слота метода start(). Именно по этой причине я удалил его из первоначальной реализации. Таким образом, инициализация объекта предпочтительно должна выполняться в конструкторе.
Ответы
Ответ 1
Чтобы сделать это, вы хотите вызвать 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 ) );
Ответ 2
О единственном улучшении, которое я бы добавил, - это сэкономить время на поиск метода:
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 ) );
}
Ответ 3
Я подозреваю, что в MyTask есть ошибка. Если я правильно понял внутренние элементы Qt, то
moveToThread(&m_thread);
не будет выполнено, если parent
не равно 0.
Ответ 4
Итак, как обернуть все это в класс?
Я также добавил слот finishPlease
, который будет добавлен как последний элемент в список todo сообщения, и даст обратную связь основной программе, когда она фактически обработала все ожидающие сообщения до того, как их можно будет убить.
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) { }
};