2010-07-21 3 views
16

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

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

EDIT: На основании предложенного ответа, вот новый код. Метод 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's asynchronous task 
    sleep(10); 
    emit taskCompleted(param*2); 
} 

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

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

+0

Я вижу проблему здесь: документы говорят, что вы не можете переместить объект на другой поток, если он имеет родителя. Работает ли ваш код, если MyTask создан с родителем? – andref

+0

Аналогичный вопрос уже был дан в [QT + Как позвонить слоту из пользовательского кода C++, запущенного в другом потоке] (http: // stackoverflow.ком/вопросы/1144240/кварта-как к вызову слота-из-обычая-с-код выполняющегося-в-другой-нити). – Trilarion

ответ

19

Чтобы сделать это, вы хотите позвонить 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)); 
+0

Это потрясающе. Отлично работает. См. Раздел «Редактирование» для используемого кода. – chmike

+4

Можно ли это сделать без использования имени строки для метода? –

3

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

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

Сколько вы можете получить благодаря этим дополнительным усилиям? –

+0

Подпись метода, переданного 'indexOfMethod', должна включать аргументы и находиться в нормализованной форме ([link] (https://qt-project.org/doc/qt-5.0/qtcore/qmetaobject.html#indexOfMethod)), поэтому правильный код должен быть 'm_doTaskImplIndex (metaObject() -> indexOfMethod (metaObject() -> normalizedSignature (" doTaskImpl (int) ")))' –

0

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

}; 
1

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

moveToThread(&m_thread);

потерпит неудачу, если parent не 0.

Смежные вопросы