Qt - QThread - wątek nie uruchamia pętli

0

Bawię się wątkami i mam taki kod

class MyAnyClass : public QThread
{
    Q_OBJECT

public:
    MyAnyClass(QObject *parent=nullptr);

private:
    QTimer *timer;

    // QThread interface
protected:
    virtual void run() override{
        qInfo()<< "Watek dziala :)";
        timer = new QTimer();

        QObject::connect(timer, &QTimer::timeout, this, [this](){
            for(int i=0; i<15; ++i){
                qInfo()<< "watek ciezko pracuje" << i;
            }
            emit endTimer();
        });

        timer->setInterval(1000);
        timer->start();

        this->exec(); //start pętli zdarzeń dla timera
    }

public slots:
    void stopTimer(){
        //sprawdzam czy timer został utworzony tj. czy jest przypisany do niego adres
        //jeżeli tak, to mogę poprawnie zakończyć timer
        if(timer){
            timer->stop();
            this->quit(); //koniec pętli zdarzeń dla timera
        }
    }

signals:
    void endTimer();
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyAnyClass thread;

    QObject::connect(&thread, &QThread::started, [](){qInfo() << "start watku";});
    QObject::connect(&thread, &QThread::finished, [](){qInfo()<< "koniec watku";});
    QObject::connect(&thread, &QThread::finished, &thread, &QThread::quit);

    thread.start();
    thread.wait();

    return a.exec();
}

błąd jest w funkcji run w miejscu rejestracji sygnałów i slotów connect

  1. dlaczego pętla w ogóle się nie uruchamia?

  2. jak zmienię zawartość w funkcji connect na

    QObject::connect(timer, &QTimer::timeout, [&](){
    

    to pętla się uruchamia ale działa w nieskończoność i sygnał endTimer(); nie uruchamia się aby zakończyć wątek.

    Czy ktoś pomoże rozwiązać problem? I co robię źle?

0

Kiedyś bawiłem się wątkami w Qt, tyle, że w okienkach zamiast w konsoli. Proponuję wątek utworzyć na stercie zamiast na stosie, czyli typ QThread*.

Ja to robiłem w okienkach, a nie w konsoli, czyli dałem np. przycisk "Start" i "Stop", które uruchamiały i kończyły pętle. u Ciebie najprawdopodobniej to zadziała

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyAnyClass * thread = new MyAnyClass(this);

    QObject::connect(thread, &QThread::started, [](){qInfo() << "start watku";});
    QObject::connect(thread, &QThread::finished, [](){qInfo()<< "koniec watku";});
    QObject::connect(thread, &QThread::finished, &thread, &QThread::quit);

    thread->start();
    thread->wait();

    // Użyj tego zamiast zwykłego delete
    thread->deleteLater();

    return a.exec();
}
0
andrzejlisek napisał(a):

Kiedyś bawiłem się wątkami w Qt, tyle, że w okienkach zamiast w konsoli. Proponuję wątek utworzyć na stercie zamiast na stosie, czyli typ QThread*.

Ja to robiłem w okienkach, a nie w konsoli, czyli dałem np. przycisk "Start" i "Stop", które uruchamiały i kończyły pętle. u Ciebie najprawdopodobniej to zadziała

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyAnyClass * thread = new MyAnyClass(this);

    QObject::connect(thread, &QThread::started, [](){qInfo() << "start watku";});
    QObject::connect(thread, &QThread::finished, [](){qInfo()<< "koniec watku";});
    QObject::connect(thread, &QThread::finished, &thread, &QThread::quit);

    thread->start();
    thread->wait();

    // Użyj tego zamiast zwykłego delete
    thread->deleteLater();

    return a.exec();
}

robiłem tak i też to nic nie daje...

1

https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html

A nie powinno być bez jednego this?

        QObject::connect(timer, &QTimer::timeout, [this](){

Sprawdź w logach czy ten connect zakończył się sukcesem

1

Nie mam teraz czasu więcej coś pisać, więc generalnie tak, jeśli reimplementujesz metodę run to powinieneś korzystać z niskopoziomowych synchronizacji. Mieszanie własnej implementacji i sygnałów wymaga dość dobrego zrozumienia event loop w qt i kilku kruczków.
Jeśli chcesz mieć sygnały i sloty w wątkach to istnieje lepszy sposób. Tworzysz QThread czysty. Następnie implementujesz zestaw QObject z sygnałami i slotami, i używasz moveToThread metody każedo z obiektów wysyłając je do wątku oraz jego event loopa.
Jeśli potrzebujesz prostej operacji która jednak może trochę trwać to polecam używać QRunable i całej otoczki wokół tego.

0

@zkubinski: Zacznij od tego przykładu https://wiki.qt.io/QThreads_general_usage
z mojego punktu widzenia jest to prostsze rozwiązanie i bardziej czytelne
ewentualnie obejrzyj gotowe rozwiązanie w Qt moze cos sie przyda https://doc.qt.io/qt-6/qtconcurrent-index.html zamiast walczyc z QThread

0
MarekR22 napisał(a):

A nie powinno być bez jednego this?

        QObject::connect(timer, &QTimer::timeout, [this](){

Sprawdź w logach czy ten connect zakończył się sukcesem

Robiłem jedno this robiłem [&](){...} i to samo - co do logów to nie wiem gdzie i jak to sprawdzić?

Herr Mannelig napisał(a):

Nie mam teraz czasu więcej coś pisać, więc generalnie tak, jeśli reimplementujesz metodę run to powinieneś korzystać z niskopoziomowych synchronizacji. Mieszanie własnej implementacji i sygnałów wymaga dość dobrego zrozumienia event loop w qt i kilku kruczków.
Jeśli chcesz mieć sygnały i sloty w wątkach to istnieje lepszy sposób. Tworzysz QThread czysty. Następnie implementujesz zestaw QObject z sygnałami i slotami, i używasz moveToThread metody każedo z obiektów wysyłając je do wątku oraz jego event loopa.
Jeśli potrzebujesz prostej operacji która jednak może trochę trwać to polecam używać QRunable i całej otoczki wokół tego.

  1. jakie to są niskopoziomowe synchronizacje?

  2. event loop z grubsza rozumiem - tylko w samouczkach nie ma nic na temat jawnego mieszania QThread z QEventLoop

  3. co to za kruczki?

  4. jeżeli mówisz o czystym QThread to czy chodzi ci mniej więcej o taki kod?

    int main(int argc, char *argv[])
    {
     QCoreApplication a(argc, argv);
    
     QThread thread;
    
     QObject::connect(&thread, &QThread::started, [](){
         qInfo()<< "thread start!!!";
         QThread::currentThread()->quit(); //już bezpośrednio w wątku trzeba go zatrzymać
     });
    
     QObject::connect(&thread, &QThread::finished, &a, [&](){
          qInfo()<< "thread stop!!!";
          a.quit();
     });
    
     thread.start();
    
     return a.exec();
    }
    

lub wersja na obiektach

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThread *thread = new QThread;
    Event *myEvent = new Event;

    myEvent->moveToThread(thread);

    QObject::connect(thread, &QThread::started, myEvent, &Event::doWork);

    QObject::connect(myEvent, &Event::workFinished, myEvent, &Event::deleteLater);
    QObject::connect(thread, &QThread::finished, thread, &QThread::quit);

    thread->start();

    QTimer *timer = new QTimer;

    QObject::connect(timer, &QTimer::timeout, thread, [&](){
        if (thread->isRunning()) {
            qDebug() << "Watek nadal pracuje";
        } else {
            qDebug() << "Watek zakonczyl prace";
            QCoreApplication::quit();
        }
    });

    timer->start(1000);

    return a.exec();
}

//KLASA

class Event : public QObject
{
    Q_OBJECT

public:
    Event(QObject *parent=nullptr);

private:
    void endThread(){
        qInfo()<< "koniec watku";
        QThread::currentThread()->quit();
    }

public slots:
    void doWork(){
        for(int i=0; i<3; ++i){
            QThread::sleep(1);
            qInfo()<< i << "Work in thread: " << QThread::currentThread();
        }
        emit workFinished();
        endThread();
    }

private slots:

signals:
    void workFinished();

};
  1. o QRundable nie słyszałem, musiałbym zobaczyć co to jest i ogarnąć
0

chyba spodziewam się gdzie może być problem? Wydaje mi się, że ta pętla nie działa, bo pracuje w innym wątku?

zrobiłem w kodzie coś takiego

timer = new QTimer(this); //dodałem this

i kompilator mi pluje taki oto komunikat

QObject: Cannot create children for a parent that is in a different thread

no i teraz jak to poprawić?

0

To jest tak, od podstaw. Bardziej niskopoziomowe prymitywy synchronizacji to są mutexy i zmienne warunkowe. Jeśli potrzebujesz tego to może też zwyczajnie dać radę std::thread. Przechodząc do Qt, tak naprawdę wywołując ręcznie exec() wymuszasz pojedyncze działanie event loopa. Tylko jak chcesz się łączyć do wątku z samego siebie w run to mamy takie zabawy jak rodzaje połączeń czy to w jakim kontekście będą wykonywane sloty (domyślnie w przypadku wątków jest to w kontekście wątku... Ale to nie jest ważne. Bo to nie jest poprawne podejście.

Drugie podejście z kodu który wysłałeśð na obiektach jest standardowym - tylko wewnątrz slotów nie używaj QThread::currentThread()->quit(); czy QCoreApplication::quit(); W tym podejściu wątki powinny być dla Ciebie przeźroczyste, najwyżej co robisz to zamykasz je z zewnątrz albo (nawet wewnętrznie!) używasz slotów za pośrednictwem sygnałów. Wtedy event loop sam wysprząta wwszystko.
Więc niewiele będzie tu zabawy per se z wątkami gdyż QThread domyślnie wszystko skrywa za fajną abstrakcją. Jak chcesz się uczyć wątków tak normalnie to std::thread ;)

0

Nie mam teraz czasu więcej coś pisać, więc generalnie tak, jeśli reimplementujesz metodę run to powinieneś korzystać z niskopoziomowych synchronizacji. Mieszanie własnej implementacji i sygnałów wymaga dość dobrego zrozumienia event loop w qt i kilku kruczków.

i te kruczki to najprawdopodobniej... co? Powiesz?

Dowiedziałem się, że ponowna implementacja funkcji "run()" jest niebezpieczna i może prowadzić do dziwnych zachowań bo:

  1. jak w wątku implementujemy funkcję "run()" to nie można w tej funkcji odwoływać się do obiektów "QWidget" - z tego względu, że Qt nie jest w stanie zarządzać operacjami GUI z innych wątków, dlatego QWidget i inne obiekty GUI muszą być używane tylko w głównym wątku aplikacji a wiadomo, w Qt prawie wszystko jest widgetem
  2. po implementacji tej funkcji jak chcę mieć pełną i w sumie poprawną obsługę "sygnałów" i "slotów" - to w funkcji "run()" muszę uruchomić pętlę zdarzeń, bo ponoć mało kto pamięta o tym, że jeśli chce się mieć obsługę sygnałów i slotów, to trzeba odpalić pętlę zdarzeń wewnątrz run() i jak to się odpali, to "niebezpieczeństwo" nieprzewidywanego zachowania programu znika, czyli:
class MyThread : public QThread
{
public:

protected:
  virtual void run() override{

    //jakiś tam kod
    this->exec(); //uruchamiam pętlę zdarzeń
  }
};

albo druga metoda na uruchomienie pętli zdarzeń

class MyThread : public QThread
{
public:

protected:
  virtual void run() override{

    QEventLoop loop;

    //jakiś kod

    loop->exec(); //uruchamiam pętlę zdarzeń
  }
};

Jeśli chcesz mieć sygnały i sloty w wątkach to istnieje lepszy sposób. Tworzysz QThread czysty. Następnie implementujesz zestaw QObject z sygnałami i slotami, i używasz moveToThread metody każedo z obiektów wysyłając je do wątku oraz jego event loopa.

tak, to wiem, o ile dobrze ciebie zrozumiałem, to chodzi o to co jest poniżej? Bo wyczytałem, że jest to najbezpieczniejszy sposób w odniesieniu do pierwszego czyli zamiast nadpisywania metody "run()" więc:

class MyThread : public QObject
{
public:
//metody...
};

int main()
{
  MyThread th;
  QThread thread;

  th->moveToThread(thread); //najbezpieczniejsze i pełna bezproblemowa obsługa sygnałów i slotów

  return 0;
}

Jeśli potrzebujesz prostej operacji która jednak może trochę trwać to polecam używać QRunable i całej otoczki wokół tego.

na chwilę obecną nie potrzebuję tego ale i tak będę musiał się z tym zapoznać

0

dobra, już sobie rozwiązałem sam problem, bo już zrozumiałem o co w tym chodzi - w Qt jest trochę trudniej, niż w std::thread
wyjaśnienie w komentarzach kodu

class MyAnyClass : public QThread
{
    Q_OBJECT

public:
    MyAnyClass(QObject *parent=nullptr);

private:
    QTimer *timer;

    // QThread interface
protected:
    virtual void run() override{
        qInfo()<< "Watek dziala :)";
        timer = new QTimer();

        QObject::connect(timer, &QTimer::timeout, this, [this](){
            for(int i=0; i<5; ++i){
                qInfo()<< "watek ciezko pracuje" << i+1;
            }
            emit endTimer();
        }, Qt::QueuedConnection); //dodałem Qt::QueuedConnection aby skutecznie obsłużyć wszystkie sygnały czekające w kolejce

        timer->setInterval(1000);
        timer->start();

        this->exec(); //start pętli zdarzeń dla timera
    }

public slots:
    void stopTimer(){
        //sprawdzam czy timer został utworzony tj. czy jest przypisany do niego adres
        //jeżeli tak, to mogę poprawnie zakończyć timer
        if(timer){
            QMetaObject::invokeMethod(timer,"stop",Qt::QueuedConnection); //dodałem tą metodę, aby zakończyć timer w kontekście tego wątku
            this->quit(); //koniec pętli zdarzeń dla timera
            this->wait();
        }
    }

signals:
    void endTimer();
    void requestStopTimer();
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyAnyClass thread;

    QObject::connect(&thread, &QThread::started, [](){qInfo() << "start watku";});
    QObject::connect(&thread, &MyAnyClass::endTimer, &thread, [&](){//łączę właściwe sygnały aby zakończyć timer
        qInfo()<< "koniec watku";
        emit thread.requestStopTimer();//emituję sygnał zakończenia timera
    });
    QObject::connect(&thread, &MyAnyClass::requestStopTimer, &thread, [&](){//
        thread.stopTimer();//jeżeli wysłałem żądanie zakończenia timera, to tutaj go kończę
        a.quit();//i przy okazji główną pętlę zdarzeń też
    });

    thread.start();

    return a.exec();
}

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.