2010-07-21 11 views
16

Mam obiekt na żywo zaimplementowany w następujący sposób. Służy do wykonywania długich zadań w tle. Główny wątek wywołuje zadania, wysyłając sygnał do publicznych gniazd (to jest doTask). Oto obnażony przykład (nie testowany).Wywołać metodę gniazda bez połączenia?

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

Ten (powinien) działać zgodnie z oczekiwaniami, dopóki doTask() jest wywoływany przez sygnał. Ale jeśli główny wątek wywoła metodę doTask() bezpośrednio, zostanie ona wykonana przez główny wątek. W przypadku niektórych zadań chcę wymusić wykonanie przez wątek bieżącego obiektu, nawet jeśli metoda jest wywoływana bezpośrednio.

Mogłem dodać kod przed funkcją doTask(), aby sprawdzić, czy bieżący wątek to m_thread, w którym to przypadku metoda jest wykonywana. Jeśli nie, chciałbym, aby doTask() wysyłał sygnał do "this", tak aby wywołanie metody doTask() było umieszczane w kolejce w pętli exec m_thread i wykonywane przez nią tak szybko, jak to możliwe.

Jak mogę to zrobić?

EDIT: W oparciu o proponowaną odpowiedź, oto nowy kod. Metoda doTask teraz deleguje wykonanie przez wątek live objet, nawet jeśli jest wywoływana bezpośrednio przez główny wątek. Wywoływany przez sygnał nadal działa zgodnie z oczekiwaniami.

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

Jest to najprostsza implementacja, jaką mogłem znaleźć, aby obsługiwać metody asynchroniczne w osobnym wątku. Wywołania metod doTask() będą kolejkowane i przetwarzane zaraz po uruchomieniu wątku. Wywoływany z wątku obiektu, zostanie wykonany natychmiast (nie w kolejce).

Należy zauważyć, że sygnał uruchomienia() jest emitowany tylko po uruchomieniu wątku. Oznacza to, że wywołanie metody doTask() w kolejce przed uruchomieniem wątku zostanie wykonane przed wywołaniem wywołania metody begin(). Z tego powodu usunąłem go z początkowej implementacji. Inicjowanie obiektów powinno zatem być korzystnie wykonywane w konstruktorze.

+0

widzę problemu w tym miejscu: docs powiedzieć, że nie można przenieść obiekt do innego wątku czy to ma rodzica. Czy Twój kod działa, gdy MyTask jest tworzony z rodzicem? – andref

+0

Podobne pytanie zostało już udzielone w [QT + Jak wywołać slot z niestandardowego kodu C++ działającego w innym wątku] (http: // stackoverflow.com/questions/1144240/qt-how-to-call-slot-from-custom-c-code-running-in-a-different-thread). – Trilarion

Odpowiedz

19

Aby to zrobić, należy zadzwonić pod numer QMetaObject::invokeMethod. W twoim przypadku, to wyglądać

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

To jest niesamowite. To działa jak urok. Zobacz mój Edytuj dla używanego kodu. – chmike

+4

Czy można to zrobić bez użycia nazwy ciągu dla metody? –

3

O jedynie do usprawnienia bym dodać to zaoszczędzić trochę czasu na wyszukiwanie metodę:

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

Ile zyskujesz dzięki temu dodatkowemu wysiłkowi? –

+0

Podpis metody przekazanej do 'indexOfMethod' powinien zawierać argumenty i być w znormalizowanej formie ([link] (https://qt-project.org/doc/qt-5.0/qtcore/qmetaobject.html#indexOfMethod)), więc poprawny kod powinien być 'm_doTaskImplIndex (metaObject() -> indexOfMethod (metaObject() -> normalizedSignature (" doTaskImpl (int) "))) –

0

Tak, jak o to wszystko do owijania fajna lekcja?
Dodałem także slot finishPlease, który zostanie dodany jako ostatni element na liście wiadomości do todo, i przekaże informację zwrotną do głównego programu, gdy przetworzy on wszystkie oczekujące wiadomości, zanim będzie mógł zostać zabity.

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

Podejrzewam, że istnieje błąd w MyTask. Jeśli mam rozumieć wewnętrzne Qt poprawnie następnie

moveToThread(&m_thread);

zawiedzie jeśli parent nie jest 0.

Powiązane problemy