[Solved] GUI unresponsive during intensive computation



  • I am writing a program that displays/calculates checksums for all files in the current directory and all directories under it. The problem is, as QDirIterator is going through all of the files calculating checksums, the GUI freezes and turns grey. GUI will only become responsive again after the function has finished.

    How do I implement threading so that the GUI remains responsive while the function is running?

    This is what I have right now. It doesn't seem to work, since if I replace the function with a for loop, the GUI is still not responding until the for loop finishes.

    MainWindow.cpp
    @
    MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    ui->textBrowser->setFont(QFont("Monospace",11));
    ui->textBrowser->setLineWrapMode(QTextEdit::NoWrap);

    updater->moveToThread(pthread);
    connect(this,SIGNAL(getHash()),updater,SLOT(getHash()));
    connect(updater,SIGNAL(req()), this, SLOT(getCheckSum()));
    connect(updater,SIGNAL(Finished()),pthread,SLOT(quit()));
    connect(pthread, SIGNAL(finished()), updater, SLOT(deleteLater()));
    

    }@

    MainWindow.h
    @
    private:
    Ui::MainWindow *ui;
    QByteArray sig;
    QString buffer;

    QThread *pthread = new QThread(this);
    GUIUpdater *updater = new GUIUpdater();
    

    @

    When user presses button:
    @
    void MainWindow::on_pushButton_clicked()
    {
    pthread->start();
    emit getHash();
    }
    @

    doWork() will work, but how do I call a function in MainWindow?
    Worker.h
    @
    void getpHash(){ emit req();}

    void doWork()
    {
        while(1)
            qDebug() << "Sleeping";
    }
    

    @



  • I don't see how your MainWindow.h code would compile - are you trying to initialize the thread and the updater members there?

    If I understand correctly, you have a worker thread, and you want to notify the main UI thread that it finished work with a certain result? You can emit a signal from that worker thread, and connect that signal to a slot in your main window.



  • Hi,
    I want to run a member function from MainWindow in a different thread. Is that possible?

    @
    MainWindow::getHash()
    @

    I only included the private member declaration part of MainWindow.h.



  • If you have something happening in a worker thread, can't you pass it all the information so that it does all the work in that thread?

    If getHash() is public, and you pass a pointer to your worker thread so that it can access the MainWindow object, then you can call getHash() and it will be executed in the worker thread. But I think this is really complicating things.



  • If you have a long procedure, perhaps executing a for loop, sprinkle some of these in your code

    qApp->processEvents(QEventLoop::ExcludeSocketNotifiers,10);

    This will give the app a few (10 in this case) milliseconds to handle window events

    A better way would be do put rethink your worker thread so it does not block



  • Okay, so I copied everything from the MainWindow::getHash() function over to a member function in the worker object. Then I connected a signal/slot and it works.

    However, I would rather just have the function be a member function of MainWindow.

    [quote author="frankiefrank" date="1401064145"]If you have something happening in a worker thread, can't you pass it all the information so that it does all the work in that thread?

    If getHash() is public, and you pass a pointer to your worker thread so that it can access the MainWindow object, then you can call getHash() and it will be executed in the worker thread. But I think this is really complicating things.[/quote]

    How would I pass a pointer to the worker thread?



  • From the code parts you posted I understand you have your Updater object which you move to the worker thread. You can store a pointer to MainWindow as a member of Updater, and set it via the constructor.

    Of course, if you access the same code both in the Main UI thread and in the worker thread - without any thread safety mechanics - you may end up in trouble.



  • I see.
    So just to be clear, is there no other way of having MainWindow::function() run in a different thread?

    I would either have to pass a pointer to a MainWindow object or just implement MainWindow::function() as a member function of the worker class?



  • Maybe there are other ways, I can't think of them right now and even these ones may be a bit too complicated for the problem you presented.



  • [quote author="frankiefrank" date="1401090123"]Maybe there are other ways, I can't think of them right now and even these ones may be a bit too complicated for the problem you presented.[/quote]

    I did some more reading and @Qtconcurrent@ seems to work.



  • I'd recommend separating your business logic/algorithm methods from your GUI classes. This would allow you to follow the object-oriented single responsibility principle better.

    If you moved your getHash function to a class of your own that inherits from QObject, you will be able to do something like this:

    @MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    ui->textBrowser->setFont(QFont("Monospace",11));
    ui->textBrowser->setLineWrapMode(QTextEdit::NoWrap);

    // Make sure not to parent updater, so you can move it to a thread
    GUIUpdater* updater = new GUIUpdater();
    QThread* pthread = new QThread(this);
    
    updater->moveToThread(pthread);
    connect(this,SIGNAL(getHash()),updater,SLOT(getHash()));
    connect(updater,SIGNAL(req()), this, SLOT(getCheckSum()));
    connect(updater,SIGNAL(Finished()),pthread,SLOT(quit()));
    connect(pthread, SIGNAL(finished()), updater, SLOT(deleteLater()));
    

    }@

    To call a slot in MainWindow from an updater method, you can use signal and slots like the req() signal you connected in the MainWindow constructor. This will be an asynchronous method call; when you emit the signal from updater, execution will continue in the updater method. The main/GUI thread will have the slot waiting in its event queue.

    If you need a direct function call, in the case where the timing is important or the doWork method needs a return value from a class on the main/GUI thread (in your case, MainWindow), I would recommend refactoring your doWork method. Make the place where you emit the signal to cause the main/GUI thread to compute be the last line of your doWork method. Then, in the class on the main/GUI thread (MainWindow), your slot will do your computation, and then emit a new signal that will be connected to a new slot containing the rest of the code that was previously in your doWork method after the signal emission.

    It will look like this:
    @MainWindow::MainWindow()
    {
    connect(worker, SIGNAL(doGUISlot()), this, SLOT(doImportantComputation()));
    connect(this, SIGNAL(doRestOfWorkSlot()), worker, SLOT(finishWork()));
    }

    Class::doWork()
    {
    // Do the first part of your work here, then emit to allow GUI to compute. GUI will signal when it's done to
    // cause finishWork to be executed

    emit doGUISlot();
    

    }

    MainWindow::doImportantComputation
    {
    // Do computation here, then go back to Worker::finishWork

    emit doRestOfWorkSlot();
    

    }

    Class::finishWork()
    {
    // Finish work
    }@


Log in to reply
 

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