2010-07-21 13 views
16

Tengo un objeto vivo implementado de la siguiente manera. Se usa para ejecutar tareas largas en segundo plano. El hilo principal invoca las tareas enviando una señal a las ranuras públicas (es decir, doTask). Aquí hay un ejemplo reducido (no probado).¿Invocar método de ranura sin conexión?

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

Esto (debería) funcionar como se espera siempre que doTask() sea invocado por una señal. Pero si el hilo principal llama a doTask() directamente, entonces será ejecutado por el hilo principal. Para algunas tareas, quiero aplicar una ejecución por el hilo del objeto en vivo, incluso si el método de ranura se llama directamente.

Podría agregar el código delante de doTask() para comprobar si el hilo actual es m_thread en cuyo caso se ejecuta el método. De lo contrario, me gustaría que doTask() emita una señal a 'esto' para que una invocación de doTask() se ponga en cola en el bucle m_thread exec y se ejecute lo antes posible.

¿Cómo podría hacer eso?

EDIT: De acuerdo con la respuesta propuesta, aquí está el nuevo código. El método doTask ahora delega la ejecución por el hilo del objeto vivo, incluso si es invocado directamente por el hilo principal. Llamado por señal todavía funciona como se esperaba.

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

Esta es la implementación más simple que pude encontrar para soportar ejecuciones de métodos asíncronos en un hilo separado. Las invocaciones de los métodos doTask() se pondrán en cola y se procesarán tan pronto como se inicie el subproceso. Cuando se llama desde el hilo del objeto, se ejecutará inmediatamente (no en cola).

Tenga en cuenta que la señal de inicio() se emite solo cuando se inicia la secuencia. Esto significa que la invocación del método doTask() en cola antes de que se inicie el hilo se ejecutará antes de invocar el intervalo del método started(). Esta es la razón por la que lo eliminé de la implementación inicial. La inicialización de objetos debería realizarse preferiblemente en el constructor.

+0

veo un problema en este caso: los documentos dicen que no se puede mover un objeto a otro hilo si tiene un padre ¿Su código funciona si MyTask se crea con un padre? – andref

+0

Ya se respondió una pregunta similar en [QT + Cómo llamar a la ranura del código personalizado de C++ que se ejecuta en una conversación diferente] (http: // stackoverflow.com/questions/1144240/qt-how-to-call-slot-from-custom-c-code-running-in-a-different-thread). – Trilarion

Respuesta

19

Quiere llamar al QMetaObject::invokeMethod para hacer esto. En su caso, se vería algo así como

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

Esto es asombroso. Funciona a las mil maravillas. Ver mi edición para el código usado. – chmike

+4

¿Es posible hacer esto sin usar el nombre de cadena para el método? –

3

Sobre la única mejora que me gustaría añadir es ahorrar algo de tiempo en buscar el método:

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

¿Cuánto gana con este esfuerzo extra? –

+0

La firma del método pasado a 'indexOfMethod' debe incluir argumentos y estar en forma normalizada ([link] (https://qt-project.org/doc/qt-5.0/qtcore/qmetaobject.html#indexOfMethod)) entonces el código correcto debe ser 'm_doTaskImplIndex (metaObject() -> indexOfMethod (metaObject() -> normalizedSignature (" doTaskImpl (int) ")))' –

0

Así que, ¿qué hay de envolver todo en una buena clase?
También he agregado una ranura finishPlease, que se agregará como último elemento en la lista de tareas para mensajes, y brinda comentarios al programa principal cuando en realidad ha procesado todos los mensajes pendientes antes de que pueda ser eliminado.

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

Sospecho que hay un error en MyTask. Si he entendido correctamente, entonces Qt internos

moveToThread(&m_thread);

fallará si no es 0. parent

Cuestiones relacionadas