Poprawne zakończenie pracy wątku QThread

0

W swojej aplikacji muszę pewną czasochłonną operacje wrzucić do nowego wątku, czytajac dokumentacje i kilka stron na temat używania QThread trafiłem na 4programmers... http://4programmers.net/Forum/C_i_C++/210633-qt_creator_-_watki

MarekR22 bardzo fajnie wytłumaczył jak to powinno wyglądać ale mimo tego mam problem oto mój kod

// moja klasa która wykonuje ową czasochłonną operacje, jej wskaźnik mam w MainWindow
utility = new Utility();

    thread = new QThread( this );
    utility->moveToThread( thread );
    thread->start();

    // tutaj zlecam wykonanie tej operacji w nowym wątku
    connect( this, SIGNAL(sendtext(QString)), utility, SLOT( findFiles( QString ) ),
             Qt::QueuedConnection );

    // tutaj dbam o usunięcie wątku gdy przestanie być potrzebny czyli wraz ze śmiercią
   //obiektu  utility bo ten wątek jest tylko i wyłącznie do obsługi tej klasy
    connect( utility, SIGNAL(destroyed(QObject*)), thread, SLOT(quit()) );
    connect( utility, SIGNAL(destroyed(QObject*)), thread, SLOT(deleteLater()) );

i to co dostaje po wyjściu z programu

QThread: Destroyed while thread is still running
QMutex: destroying locked mutex

0

quit każe zakończyć się pętli eventów, a ta może się zakończyć jeśli zyska kontrolę (gdy wątek nic nie przetwarza).
Twój slot findFiles zapewne wywołuje deleteLater, i przetwarzanie tego sygnału emituje sygnał destroyed zanim destruktor skończył pracę (obiekt nie istniejący nie może emitować sygnałów) i ten destruktor wykonywane w przypisanym wątku nadal trzyma wątek i nie pozwala pętli zdarzeń się zakończyć. W efekcie masz pewną formę race condition, która pętla zdarzeń dostanie pierwsza kontrolę, czy ta z głównego wątku będzie przetwarzać delateLater na obiekcie wątku, czy te z tego wątku zdarzy zyskać kontrolę po zniszczeniu obiektu utility.
Rozwiązanie czekać na wykonanie zniszczenia obiektu inaczej łącząc sygnały i sloty:

connect( utility, SIGNAL(destroyed(QObject*)), thread, SLOT(quit()) );
connect( thread, SIGNAL(finished()), thread, SLOT(deleteLater()) );
0

Po zmianie na

connect( utility, SIGNAL(destroyed(QObject*)), thread, SLOT(quit()) );
connect( thread, SIGNAL(finished()), thread, SLOT(deleteLater()) );

nadal pozostaje błąd

QThread: Destroyed while thread is still running

0

Jeżeli chodzi o slot findFiles( QString ) to wyglada to tak

QList< QPair< QString, quint32 > > Utility::findFiles( const QString & source_name )
{
     QList< QPair< QString, quint32 > > scores;

     // tutaj wyszukuje pewne rzeczy ale nie mam tu żadnych sygnałów ani tez nic nie emituje

     return scores;
}
1

Dobra doczytałem dokumentację i widzę, że faktycznie błąd nadal może występować: http://qt-project.org/doc/qt-5.0/qtcore/qthread.html#finished

Qt documentation napisał(a)

This signal is emitted from the associated thread right before it finishes executing.

When this signal is emitted, the event loop has already stopped running. No more events will be processed in the thread, except for deferred deletion events. This signal can be connected to QObject::deleteLater(), to free objects in that thread.

Czyli jeśli dobrze rozumiem, sygnał finished jest emitowany zaraz przed po tym jak event loop wie, że ma się zakończyć, ale jeszcze moze przetwarzać sygnały i eventy takie jak te wysyłane przez deleteLater (zapewne event loop działa aż do opróżnienia kolejki).
Jak to obejść? Wygląda, na to że konieczne jest wywołanie wait i dopiero po tym można niszczyć obiekt wątku.

0

To teraz jeżeli ja dobrze rozumiem to co napisałęś czyli

Wygląda, na to że konieczne jest wywołanie wait i dopiero po tym można niszczyć obiekt wątku

U mnie wątek jest niszczony razem z obiektem Utility a że i watek i obiekt Utility są wskaźnikami w klasie MainWindow to najbardziej odpowiednim miejscem aby wywołać wait() jest destruktor MainWindow

MainWindow::~MainWindow()
{
    thread->wait( 0 );
    delete utility;
    delete ui;
}

ale nadal jest to samo... gdzie mogę popełniać błąd? może powinienem "recznie" emitować jakiś sygnał z klasy Utility o zakończeniu?

1

teraz źle użyłeś wait. Parametr dla wait określa jak długo będziesz czekał na zakończenie wątku, ty dałeś zero więc wait nie czeka i zwraca ci false, bo watek się jeszcze nie zakończył.
Powinno być:

MainWindow::~MainWindow() {
    bool finished = thread->wait(5000);
    if (!finished)
         qCritical("Thread didn't end in specified time");
    delete thread;
    delete ui;
}

a pozostałe połączania sygnałów i zlotów do niszczenia tego obiektu mogą/powinny zostać.

0

Nawet jak dam

bool finished = thread->wait( 20000 );

to i tak cały czas zwraca false i komunikat...

QThread: Destroyed while thread is still running

0

To znaczy, że coś ci wisi na tym wątku, zakładam, ze ten twój slot findFiles(QString) nadal nie zakończył pracy.

0

To w takim razie nie mogę zakończysz działania i usunąć wątku kiedy mi się podoba? Chciałem to mieć na tej zasadzie że naciskam przycisk cancel klasa Utility kończy działanie, jest usuwana a wraz z usunięciem Utility usuwam tez wątek. Tylko że klasa Utility się usuwa a już wątek nie bardzo.

A dlaczego w momencie gdy ten connect nawet nie dojdzie do skutku

connect( this, SIGNAL(sendtext(QString)), utility, SLOT( findFiles( QString ) ),
             Qt::QueuedConnection );

bo nie nacisnąłem buttona i nie wyemitowałem w ogóle sygnału sendtext(QString) to po zamknięciu aplikacji również mam komunikat o przedwczesnym usunięciu wątku? Żadnej innej pracy mu nie zlecam to jest jedyny connect który uruchamia funkcję w nowym wątku...

1

Mogłem cię o to zapytać od razu :/
Nie nie można tak sobie zakończyć wątku, z prostej przyczyny: nie wiadomo, w którym miejscu przetwarzania jest wątek, co może doprowadzić do utraty ważnych zasobów (nieodblokowany mutex, niezamknięty plik, niezwolniona pamięć, itd).
By sobie umożliwić wątkowi szybkie zakończenie bez takich ekscesów, funkcja, która się w nim wykonuje, powinna sprawdzać cyklicznie wartość jakiejś flagi i w reakcji na jej wartość powinna sama się natychmiast zakończyć.
Zwykle to wygląda tak:

class TwojWorker {
    Q_OBJECT

    bool stopNowFlag;
    QMutex stopNowFlagMutex;
    ....

public:
    bool shouldStop() {
         QMUtexLocker lock(stopNowFlagMutex);
         return stopNowFlag ;
    }
public slots:
    void cancel() {
         QMUtexLocker lock(stopNowFlagMutex);
         stopNowFlag = true;
    }

    void findFiles(const QString &files) {
         while(!shouldStop()) {
              /// rob co masz robić
              ....
         }
    }
};

Przy czym pamiętaj, że połączanie z cancel() muszą być typu Qt::DirectConnection by to zadziałało inaczej to połączenie będzie próbowało skorzystać z event loopa!

0

Wstyd mi że nie sprawdziłem tego wcześniej.... a może to być jakaś podpowiedź gdzie leży problem. Mianowicie w momencie gdy zamienię

QThread * thread = new QThread( this ); // parent == MainWindow

na

QThread * thread = new QThread();

wszystko działa poprawnie - nie dostaje komunikatów o przedwczesnym usunięciu wątku...

1 użytkowników online, w tym zalogowanych: 0, gości: 1