[SOLVED] Proper handling of QThread on main window close



  • Hi all,

    I've just started learning Qt and while most of it seems relatively easy to understand, I am having an issue with properly handling QThreads. Particularly, I can't figure out what's the proper way to terminate a running QThread when the overall application exits.

    For example, I have a worker object defined as

    void FileSearchWorker::search()
    {
        quint32 found;
        found = recursiveSearch(dir);
        emit finished(found);
    }
    
    quint32 FileSearchWorker::recursiveSearch(const QDir &dir)
    {
        quint32 count = 0;
        QFileInfoList entries;
        if (dir.exists()) {
            entries = dir.entryInfoList(QDir::Files | QDir::NoSymLinks);
            for (int i = 0; i < entries.size(); i++) {
                bool is_match = false;
                if (pattern) {
                    if (pattern->exactMatch(entries.at(i).canonicalFilePath()))
                        is_match = true;
                } else
                    is_match = true;
    
                if (is_match) {
                    count++;
                    emit fileFound(entries.at(i).canonicalFilePath());
                }
            }
    
            entries = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
            for (int i = 0; i < entries.size(); i++)
                count += recursiveSearch(entries.at(i).canonicalFilePath());
        }
        return count;
    }
    

    In another class in the main thread, I have the following code which creates and sets up the worker object:

    quint32 Cscope::findAllFiles(const QDir& dir)
    {
        thread = new QThread;
        FileSearchWorker *worker = new FileSearchWorker(dir);
    
        worker->moveToThread(thread);
        connect(thread, SIGNAL(started()), worker, SLOT(search()));
        connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
        connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
        connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    
        connect(worker, SIGNAL(finished(quint32)), this, SLOT(searchFileDone(quint32)));
    
        thread->start();
        thread_is_running = true;
        while (thread_is_running)
            qApp->processEvents();
        return filesFound;
    }
    

    I've also connected the application's aboutToQuit() signal and the main window's destroyed() signal to a slot which calls thread->exit().

    The problem is that when the main window is closed, the slot to terminate the thread does not get called until the thread is finished. Considering that the main thread is spinning on thread_is_running and executing the main even loop, this does not make sense to me. I can't figure out why the terminate slot is getting called after the thread terminates on its own.

    Can someone please explain to me how to properly terminate a running thread when the main window of the application is closed?

    Thank you in advance for your advice!



  • Why are you doing this?

      while (thread_is_running)
         qApp->processEvents();
    

    Anyway, keep in mind that calling the thread::exit method will call QEventLoop::exit, and the EventLoop will only terminate once the control is returned to the EventLoop. In your case this will happen after the FileSearchWorker::search has finished.

    So you should do the following:

                         your slot connected to the aboutToQuit signal
                          {
                           thread->quit();
                           thread->wait();
                         }
    

    If you want to terminate a thread right away you have to use QThread::terminate but you shouldn't do that.



  • Thank you for your reply.

    I had the while loop because I wanted a blocking semantic from that function (it was just to test QThread). However, I didn't want to block the entire application, so I decided on spinning and processing events.

    The issue that I was having is not that I was using Qthread::exit() but rather that the slot which called thread->exit() did not get executed until the thread was done. The end effect was that the main thread event loop appeared serialized with the QThread event loop.

    I would have expected that the two event loops are disjoint and that when a signal is emitted in the main event loop the slot is called as soon as the main even loop processes that event regardless of the state of the QThread's event loop.



  • I don't know if this the best way but for programs that have threads I have a custom ::closeEvent(QCloseEvent *event) function. When the application is closed if any threads are still running the close event is ignored and the threads are shutdow (cleanly) at that point. The closeEvent() is refired using a QTimer after a short delay and keeps doing this until all threads are stopped.



  • @Rondog Thank you for the response and for the idea. I may have to do something similar. However, I cannot imagine that this is the way QThreads were intended to be used. There has to be a proper way to shut down running threads, especially considering that event-based GUI toolkits are asynchronous by nature.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Where is thread_is_running set to false ?

    Anyway, if you need a blocking behavior AND since you are using a thread, you can use the BlockingQueuedConnection rather than a while loop. One other thing to do is to design your worker to be cancelable so you can break it when needed. One other thing to check is QtConcurrent, it's higher level and might also provide you with a simpler solution.

    Finally, do you really need it to be blocking ?



  • @SGaist Hi, thank you for your reply. The blocking semantic was for testing purposes only. It wouldn't make much sense to keep it blocking but also use QThread.

    All of the suggestions on this thread are great but they still don't address the main problem - the fact that the event ("aboutToQuit") which is supposed to signal the cancellation of the thread was not being processed until after the thread exited. Below, I've include some output from my application illustrating the issue:

    This first output is from an uninterrupted, complete run:

    1431358733329 search search process done
    1431358733329 sourceThreadDone found 37755 files List size: 37755
    1431358738989 on_pushButton_2_clicked exit button clicked
    1431358738989 close close firing
    1431358738989 terminate terminate called false

    As you can see, the process found 37755 files in 4889 msecs. The second output is from a run in which I hit the exit button (which is supposed to exit the entire application):

    1431358767141 scan 49289
    1431358769218 on_pushButton_2_clicked exit button clicked
    1431358769219 close close firing
    1431358769219 terminate terminate called true
    1431358769219 terminate stopping thread
    1431358772037 search search process done
    1431358772055 sourceThreadDone found 37755 files List size: 37755

    When the thread exited, it had still found the same number of files and ran for 4914 msec. This is where the "cancelable worker" would come in handy. Finally, a run where I hit the 'X' button on the window decoration:

    1431359204324 scan 49289
    1431359209146 search search process done
    1431359209146 sourceThreadDone found 37755 files List size: 37755
    1431359209177 close close firing
    1431359209178 terminate terminate called false

    In this last run, no events have fired when the application was closed. Mind you, I have mainWindows::destroyed() connected to the same slot as QApplication::aboutToQuit() so theoretically, the same slot should be called when the "X" buttons is pressed.



  • I played around with the code a bit more and I have found that subclassing QThread offers better thread management in my use case.

    Now, I still have to figure out how to properly terminate the thread if the "X" button is clicked on the window's title bar.



  • I found that having your own version of closeEvent() will be called regardless of how the application is closed (including the 'X' at the top).

    I think I have looked at QApplication::aboutToQuit() before and found it didn't fire under all conditions. I tend to stick to what works for me so I don't regularly look at alternatives.

    I prefer sub-classing QThread as I can add options to shut it down in a way that is clean and fast. I usually add a member called 'Stop_Thread()' which sets a boolean value to true and is tested at an appropriate place in the thread itself:

    void MyThread::run(void)
    {
      d_thread_cancelled = false;  // indicates data is usable
    
      do
      {
        if(d_stop_thread)
        {
          d_thread_cancelled = true;
          return;
        }
    
      // something thread related
    
      } while(!done);  // thread runs until it is done
    }


  • @Rondog Thanks for the advice. I'll look into the closeEvent() method.



  • @voidtrance said:

    @Rondog Thanks for the advice. I'll look into the closeEvent() method.

    The MainWindow::closeEvent() provided exactly what I needed.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.