QThread and Qwidget

Alles rund um die Programmierung mit Qt
Antworten
Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

QThread and Qwidget

Beitrag von Aenni »

Hallo liebe Qt-Experten,

ich wollte mich bischen in Qthreads einarbeiten und habe mir dafür ein simples Beispiel (starte thread per button und arbeite nebenlaeufig die schleife ab -Gui soll funktionsfaehig bleiben) ausgedacht. Nur irgendwie möchte das nicht so wie ich mir das vorstelle ;)

leider hat mir google und die sufu nicht weiter geholfen :(

kurz als Übersicht für euch: Ich erstelle ein Widget, dieses Widget startet per Button einen Thread und in diesem Thread läuft eine relativ lange schleife. Die schleife macht nichts anderes wie ein directory durchzusuchen (hab nur was gebraucht was laenger dauert;). Anbei mein Code danach das Problem ;)
widget.h

Code: Alles auswählen

#ifndef WIDGET_H
#define WIDGET_H
#include <threadTest.h>
#include <QWidget>

namespace Ui {
    class Widget;    
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_clicked();

    void on_commandLinkButton_clicked();

private:
    Ui::Widget *ui;
    threadTest *tt;
};

#endif // WIDGET_H

widget.cpp

Code: Alles auswählen

#include "widget.h"
#include "ui_widget.h"
#include <QtGui>
#include <QtCore>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->lineEdit->setText(QDir::currentPath());
    tt = new threadTest(ui);
}

Widget::~Widget()
{
    delete tt;
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    QString directory = QFileDialog::getExistingDirectory(this,
                               tr("Search Directory"), QDir::currentPath());
    ui->lineEdit->setText(directory);
}

void Widget::on_commandLinkButton_clicked()
{
    tt->start();
}

threadTest.h

Code: Alles auswählen

#ifndef THREADTEST_H
#define THREADTEST_H
#include <QThread>
#include <QWidget>
#include "ui_widget.h"


class threadTest : public QThread
{
    Q_OBJECT

public:
    threadTest(Ui::Widget *ui2){
        ui = ui2;
    }

    ~threadTest(){
        delete ui;
    }
    void start();

protected:
    void run();
private:
    Ui::Widget *ui;
};

#endif // THREADTEST_H

threadTest.cpp

Code: Alles auswählen

#include "threadTest.h"
#include <QtCore>
#include <QtGui>

void threadTest::run(){
    QString dirStr = ui->lineEdit->text().trimmed();
    if (!dirStr.isEmpty()){
        QDirIterator it(dirStr, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
        while (it.hasNext()) {
            it.next();
            //FollowSymlinks falsch verstanden ? daher selber "." und ".." abfangen...
            if (it.fileName() == QLatin1String(".") || it.fileName() == QLatin1String("..")){
            } else{
                if(!it.fileInfo().isDir()) {
                    qDebug() << it.filePath();
                    ui->lbStatus->setText(it.filePath());
                    qApp->processEvents();
                }
            }
        }
    } else {
        qDebug() << "dir is empty";
    }
    //exec();
}

void threadTest::start(){
    run();
}

main

Code: Alles auswählen

#include <QtGui/QApplication>
#include "widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

Wenn ich nun den das programm starte und den thread laufen lasse, kann ich das Fenster bewegen, dann pausiert die Ausgabe(label wird nicht mehr geupdated) was wohl daran liegt, dass die eventloop nun das fenster-wird-verschoben event hat und somit das andere event hinten anfuegt bzw ein update-paused?(meine vermutung). Ich erreiche ein Update des labels auch nur dann wenn ich qApp->processEvents() aufrufe, mit exec() funktioniert es leider nicht.

Wenn ich das Programm nun schliesse, laeuft der thread weiter, d.h qDebug gibt fröhlich weiter Infos aus. Wäre es möglich sobald der Thread gestartet wird, dass im mainprogramm ein Cancel button erscheint, dieser soll wenn man drauf klickt den thread zerstören bzw automatisch ausgeführt werden, wenn das Widget geschlossen wird.

Ich vermute ich hab einen ganz schlechten Stil/weg gewählt und daher freue ich mich über eure Ratschläge/Verbesserungen.

Merci im Voraus.

Grüsse
DBGTMaster
Beiträge: 190
Registriert: 19. August 2010 10:00

Re: QThread and Qwidget

Beitrag von DBGTMaster »

Hallo,

wer hat dir erlaubt, die Methode "start()" zu überladen??

lG
Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

Re: QThread and Qwidget

Beitrag von Aenni »

DBGTMaster hat geschrieben:Hallo,

wer hat dir erlaubt, die Methode "start()" zu überladen??

lG
mhmh oke das werde ich dann mal rückgänig machen ;)

Das ganze sieht jetzt schon sehr viel besser aus. Jedoch habe ich das Problem, dass sobald ich das MainFrame in der grösse verändere ein Fehler kommt:
"QObject::killTimers: timers cannot be stopped from another thread".
Und sobald ich erneut den Button clicke, startet er nicht mehr neu?

wiedermal danke für die hilfe ;)
DBGTMaster
Beiträge: 190
Registriert: 19. August 2010 10:00

Re: QThread and Qwidget

Beitrag von DBGTMaster »

Aenni hat geschrieben:
DBGTMaster hat geschrieben:Hallo,

wer hat dir erlaubt, die Methode "start()" zu überladen??

lG
mhmh oke das werde ich dann mal rückgänig machen ;)

Das ganze sieht jetzt schon sehr viel besser aus. Jedoch habe ich das Problem, dass sobald ich das MainFrame in der grösse verändere ein Fehler kommt:
"QObject::killTimers: timers cannot be stopped from another thread".
Und sobald ich erneut den Button clicke, startet er nicht mehr neu?

wiedermal danke für die hilfe ;)
MainFrame -> Das Hauptfenster im Mainthread?
Größe verändert -> Also das Fenster größer ziehst?

// Edit: Ich bin mir gerade gar nicht sicher, ob man einen QThread doppelt starten kann, oder ob man ein neues Objekt erzeugen muss und diesen danach starten..?
Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

Re: QThread and Qwidget

Beitrag von Aenni »

DBGTMaster hat geschrieben:
MainFrame -> Das Hauptfenster im Mainthread?
Größe verändert -> Also das Fenster größer ziehst?

// Edit: Ich bin mir gerade gar nicht sicher, ob man einen QThread doppelt starten kann, oder ob man ein neues Objekt erzeugen muss und diesen danach starten..?
mainframe -> ist das Hauptframe im hauptthread.(widget)

Größe verändert -> richtig, fenster ziehen ;)

Ich habe den code im Button etwas verändert

Code: Alles auswählen

void Widget::on_commandLinkButton_clicked()
{
    tt = new threadTest(ui);
    tt->start();
    tt->wait();

}
Bei der Initialisierung von tt = new threadTest(ui), versucht mir das Programm ein Assert-Fehler den ich nicht ganz verstehe:
Fehlermeldung: Expression: _BLOCK_TYPE_OS_VALID(pHead->nBlockUse).

und leider updated er mir das label nicht während er sucht. Obwohl qApp->processEvents() aufgerufen wird :(

Vielleicht weiss ja jemand woran das liegen kann ? ;)

merci
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Re: QThread and Qwidget

Beitrag von franzf »

Du greifst aus deinem Thread direkt auf die Gui zu. Letztere läuft im HauptThread. Du darfst Gui-Operationen NUR aus dem Hauptthread heraus machen. Wenn du willst, dass sich etwas ändert, musst du das über Thread-Grenzen hinaus per SIGNAL/SLOT realisieren.
Heißt: Ein eigenes SIGNAL in deinem Thread deklarieren und in run statt dem direkten Zugriff auf ui dein SIGNAL emittieren.
Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

Re: QThread and Qwidget

Beitrag von Aenni »

franzf hat geschrieben:Du greifst aus deinem Thread direkt auf die Gui zu. Letztere läuft im HauptThread. Du darfst Gui-Operationen NUR aus dem Hauptthread heraus machen. Wenn du willst, dass sich etwas ändert, musst du das über Thread-Grenzen hinaus per SIGNAL/SLOT realisieren.
Heißt: Ein eigenes SIGNAL in deinem Thread deklarieren und in run statt dem direkten Zugriff auf ui dein SIGNAL emittieren.
Herzlichen Dank für die Info, ich hab das umgesetzt, leider ohne Erfolg.

hier meine Signal/slot einbindung:
threadtest.cpp

Code: Alles auswählen

void threadTest::run(){
    QString dirStr = ui->lineEdit->text().trimmed();
    if (!dirStr.isEmpty()){
        QDirIterator it(dirStr, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
        while (it.hasNext()) {
            it.next();
            //FollowSymlinks falsch verstanden ? daher selber "." und ".." abfangen...
            if (it.fileName() == QLatin1String(".") || it.fileName() == QLatin1String("..")){
            } else{
                if(!it.fileInfo().isDir()) {
                    qDebug() << it.filePath();
                    emit sendText(it.filePath());
                    //ui->lbStatus->setText(it.filePath());                    
                }
            }
        }
    } else {
        qDebug() << "dir is empty";
    }
}
widget.cpp

Code: Alles auswählen

void Widget::on_commandLinkButton_clicked()
{
    tt = new threadTest(ui);
    connect(tt,SIGNAL(sendText(QString)),this,SLOT(test(QString)));
    tt->start();
    tt->wait();

}

void Widget::test(QString testIt){
    ui->lbStatus->setText(testIt);
}

Leider updated sich das label auch mit dem Signal/slot nicht. Habe ich ggf. was falsch verstanden ?
Wenn ich eine MsgBox im Slot einbaue passiert folgendes: er läuft durch die schleife im Thread, wenn diese fertig ist emitiert er die msgboxen und updated das label.das heisst er emittet richtig, aber erst nachdem er die schleife durchlaufen hat:)

Wieso läuft mein Thread nicht richtig im "Hintergrund" ? Oder habe ich Prinzipiell was falsch verstanden?

Wiedermal danke ;)
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Re: QThread and Qwidget

Beitrag von Christian81 »

Doku zu QThread:.wait() lesen hilft.
Abgesehen davon wir der Slot immer noch nicht im MainThread ausgeführt da das threadTest - Objekt im MainThread lebt. -> Doku 'QThread::moveToThread' bzw. Foren-Suche.
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

Re: QThread and Qwidget

Beitrag von Aenni »

Vielen Dank für die Info Christian81,
ich habe entsprechend meine files abgeändert und soweit funktioniert auch alles ganz wundervoll ;)
NUr ein Problem habe ich mit meinem cancel Button, ich habe ein Cancelbutton erstellt, und mit dem Slot:stopProcess() connected.
Leider wird dieser slot nie getriggered.
Herzlichen Dank im Voraus ;).

threadTest.h

Code: Alles auswählen

#ifndef THREADTEST_H
#define THREADTEST_H
#include <QThread>
#include <QMutex>
#include "ui_widget.h"


class threadTest : public QThread
{
    Q_OBJECT
public:
    threadTest(Ui::Widget *ui2){
        this->moveToThread(this);
        ui = ui2;
        m_abort = false;
    }

    ~threadTest(){
        mutex.lock();
        m_abort = true;
        mutex.unlock();

        wait();
    }
    void doItNow(const QString strPath);

signals:
    void sendText(QString);

public slots:
    void stopProcess();

protected:
    void run();


private:
    Ui::Widget *ui;
    bool m_abort;
    QMutex mutex;
    QString m_path;
};

#endif // THREADTEST_H
threadTest.cpp

Code: Alles auswählen

#include "threadTest.h"
#include <QtCore>
#include <QtGui>

void threadTest::run(){    
    QString dirStr = m_path;
    if (!dirStr.isEmpty()){
        QDirIterator it(dirStr, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
        while (it.hasNext()) {
            it.next();
            //FollowSymlinks falsch verstanden ? daher selber "." und ".." abfangen...
            if (it.fileName() == QLatin1String(".") || it.fileName() == QLatin1String("..")){
            } else{
                if(!it.fileInfo().isDir()) {
                    qDebug() << it.filePath();
                    emit sendText(it.filePath());
                    if (m_abort)
                        return;
                    msleep(10);
                }
            }
        }
    } else {
        qDebug() << "dir is empty";
    }
}

void threadTest::stopProcess(){
    qDebug() << "stop process geclicked";
    mutex.lock();
    m_abort = true;
    mutex.unlock();
}

void threadTest::doItNow(const QString strPath){
    m_path = strPath;
    m_abort = false;
    start();
}

widget.h

Code: Alles auswählen

#ifndef WIDGET_H
#define WIDGET_H
#include <threadTest.h>
#include <QWidget>

namespace Ui {
    class Widget;    
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_clicked();
    void test(const QString str);
    void on_commandLinkButton_clicked();
    void resetUi();

private:
    Ui::Widget *ui;
    threadTest *tt;
};

#endif // WIDGET_H
widget.cpp

Code: Alles auswählen

#include "widget.h"
#include "ui_widget.h"
#include <QtGui>
#include <QtCore>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->lineEdit->setText(QDir::currentPath());
    tt = new threadTest(ui);

    connect(ui->btCancel, SIGNAL(clicked()), tt, SLOT(stopProcess()));
    connect(tt,SIGNAL(sendText(QString)),this,SLOT(test(QString)));
    connect(tt, SIGNAL(finished()),this,SLOT(resetUi()));
}

Widget::~Widget()
{   
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    QString directory = QFileDialog::getExistingDirectory(this,
                               tr("Search Directory"), QDir::currentPath());
    ui->lineEdit->setText(directory);
}

void Widget::on_commandLinkButton_clicked()
{
    tt->doItNow(ui->lineEdit->text());
}

void Widget::test(const QString str){
    ui->lbStatus->setText(str);
}

void Widget::resetUi(){
    ui->lbStatus->setText("fertig");
}

Aenni
Beiträge: 79
Registriert: 15. Juli 2010 22:29

Re: QThread and Qwidget

Beitrag von Aenni »

ich hab jetzt eine möglichkeit gefunden wie ich den thread "stopen" kann.
Ich schicke ein signal vom cancel button an meine hauptthread und der slot wo aufgerufen wird, ruft explizit nochmal thread->stopProcess() slot auf.
code:

Code: Alles auswählen

    
//vorher - funktioniert nicht (wird nie getriggered)
//connect(ui->btCancel, SIGNAL(clicked()), tt, SLOT(stopProcess()));

//nacher - funktioniert
onnect(ui->btCancel, SIGNAL(clicked()), this, SLOT(on_pushButton_2_clicked()));

//entsprechend slot fuer pushbutton_2
void Widget::on_pushButton_2_clicked()
{
    tt->stopProcess();
}
Da ich gerne verstehe was ich mache, würde ich mich sehr freuen wenn mir jemand das obige erklären könnte;)

lg

Aenni
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Re: QThread and Qwidget

Beitrag von solarix »

funktioniert nicht (wird nie getriggered)
Ganz einfach: stelle dir das Signal/Slot-Konzept in Threads vor wie eine "Auftrag" (oder von mir aus einen Brief in den Briefkasten des Threads). Wird der Button "btCancel" bei

Code: Alles auswählen

connect(ui->btCancel, SIGNAL(clicked()), tt, SLOT(stopProcess()));
geklickt, wird im Eventloop des Threads(!) den Auftrag hinterlegt: "bitte führe den Slot 'stopProcess()'" aus. Und dieser Auftrag wird also erst ausgeführt, wenn sich der Thread im Eventloop (exec()) befindet. Oder zurück zum "Briefkasten"-Vergleich: wenn du die Post nie abholst, erfährst du auch deine Aufträge nicht ;)

Mögliche Lösungen:
1. Entweder eine "DirectConnection" (siehe http://doc.qt.nokia.com/latest/qt.html# ... nType-enum). Dann wird der Slot nicht "gequeued" sondern direkt (im Kontext des GUI-Threads) ausgeführt.
2. Der Eventloop explizit antriggern (z.B. als Ersatz oder Ergänzung zum "msleep(..)"). IMHO müsstest du dazu eine lokale "QEventLoop" in der run-Methode erstellen.. aber da bin ich mir gerade nicht sicher.
3. Auf Signals/Slot verzichten.. wie du es gemacht hast ;)


Noch was anderes: Mutexe werden eingesetzt, um gleichzeitiges Lesen UND Schreiben zu verhindern.. das bringt nur was, wenn man also die Variabel (in deinem Fall "m_abort") auch beim Lesen (if (m_abort) ist "Lesen") schützt...

hth...

[EDIT]
Ich glaube ich würde einen anderen Konstruktor mit den "FilterFlags" von QDirIterator vorziehen.. mit "QDir::NoDotAndDotDot" als Filter-Argument könntest du vermutlich auf den hässlichen "if" verzichten..
Antworten