Sailanarmo
Sailanarmo

Reputation: 1191

How to keep std::thread from freezing GUI in QT?

I am working on a project where I will be ingesting multiple binary files, decode them, and convert their data into a CSV. I figured the quickest way to do this would be to thread the work. Simply load the files into a queue, have the threads grab a file, work on it, convert it, output it, and then die.

What I wrote actually works great, however, I cannot figure out how to get the GUI to be responsive as I have a progress bar that I would like to update or simply have the user move the GUI to a corner while it processes the data. And I believe this is because std::thread is just hanging up the GUI.

In my code I have the following function once a button is pressed to execute:

void MyExtractor::on_Execute_clicked()
{
    QStringList binary = tlmFiles.entryList(QStringList() << "*.bin",QDir::Files);
    queue.clear();
    threadPool.clear();

    if(binary.size() != 0)
    {

        foreach(QString filename, binary)
        {
            queue.emplace_back(inputDir + '/' + filename);
        }

        for (unsigned int i = 0; i < std::thread::hardware_concurrency(); ++i)
        {
            threadPool.emplace_back(&MyExtractor::initThread,this,std::ref(queue),std::ref(mut));
        }

    }
    else
    {
        message.setText("No binary files found! Please select another folder!");
        message.exec();
    }

    for (auto &&e : threadPool)
    {
        e.join();
    }
}

And initThread looks like this:

void MyExtractor::initThread(std::deque<QString> &queue, std::mutex &mutex)
{
    QString file;
    QString toOutput = outputDir;

    while(queue.size() > 0)
    {
        {
            std::lock_guard<std::mutex> lock(mutex);
            if(!queue.empty())
            {
                file = queue.front();
                queue.pop_front();
            }
        }
        BitExtract *bitExtractor = new BitExtract();
        if(file.size() != 0)
        {
            bitExtractor->extract(file,toOutput);
        }
        delete bitExtractor;
    }

}

I have been reading about QThreads. And from what I think I have been reading, it seems I need to create a separate thread to watch the work, and the other thread to watch the GUI? I am not sure if I have worded that correctly. However, I am not even sure how to go about that since I am using a std::thread to do the conversion, and I am not sure how well QThread will play with this. Any suggestions?

EDIT: I should make it clear that threadPool is a std::vector<std::thread>

Upvotes: 0

Views: 1131

Answers (1)

Jeremy Friesner
Jeremy Friesner

Reputation: 73364

As noted by @drescherjm, your problem is here:

for (auto &&e : threadPool)
{
    e.join();
}

join() won't return until the thread has completed, which means your GUI thread will be blocked inside that for-loop until all threads have exited, which is what you want to avoid. (it's always desirable for any function in the main/Qt/GUI thread to return as quickly as possible, so that Qt's GUI event loop can remain responsive)

Avoiding that is fairly straightforward -- instead of calling join() right after the threads have been spawned, you should only call join() on a thread after the thread has notified you that it has completed its work and is about to exit. That way join() will never take more than a few milliseconds to return.

As for how to get a std::thread to notify your main/GUI thread that it has finished its task, one simple way to do it is to have your std::thread call QApplication::postEvent() just before it exits, and override the event(QEvent *) virtual method on (whatever object you passed in as the first argument to postEvent()) to handle the posted event-object (note that you can make your own subclass of QEvent that contains whatever data you want to send to the GUI thread) by calling join() on the std::thread, plus whatever cleanup and result-handling code you need to execute after a thread has returned its result.

Upvotes: 3

Related Questions