Andy
Andy

Reputation: 3799

How can I communicate back the result from a std::thread to the Gui main thread in Qt?

In order to learn about threading in Qt and C++ I am creating a small example program. It has a Gui with a button and a text field:

enter image description here

When the user presses the Calculate button it calculates pi using a sum formula. In order for the Gui to be responsive during this lengthy operation the calculation will be performed in a separate thread.

First I created a subclass of QThread that does the calculation in its run() method and emits the signal void resultReady(double value); when it is finished. This signal I connected to the slot void setResult(double value); in my Dialog Gui class. This approach works fine.

Now I want to do the same thing using std::thread. How do I do this? I am having problems communicating the result back to the Gui. I tried this:

class StdThreadStrategy : public QObject {
public:
    void doTheWork() {
        double pi = pi_sum();
        QTimer::singleShot(0, this, [=] { dialog->setResult(pi); });
    }
    // This is called by Dialog when the user presses the Calculate button:
    void calculatePi(Dialog* dialog) {
        this->dialog = dialog;
        std::thread t(&StdThreadStrategy::doTheWork, this);
        thread = std::move(t);
    }
private:
    Dialog* dialog;
    std::thread thread;
};

A StdThreadStrategy object is constructed in the constructor of Dialog:

Dialog::Dialog() : QDialog() {
  // .. create gui code
  calculatePiStrategy = new StdThreadStrategy();
}

// this is the slot I want called from the other thread:
void Dialog::setResult(double value) {
  piLineEdit->setText(QString::number(value));
}

// Called by Calculate button:
void Dialog::calculate() {
  calculatePiStrategy->calculatePi(this);
}

I was hoping using QTimer::singleShot in the doTheWork() method would allow me to post to the event queue of the Gui from another thread. Unfortunately I get the error message: QObject::startTimer: Timers can only be used with threads started with QThread.

How can I communicate back the result from a std::thread to the Gui main thread in Qt?

Upvotes: 0

Views: 809

Answers (3)

Ext3h
Ext3h

Reputation: 6393

Add a signal to your instance of StdThreadStrategy, and connect that via an explicitly deferred connection to the handler living in the UI thread. That way, you can safely call the signal from any thread, Qt takes care of sending it where it should go.

Also don't forget to join() your thread in the destructor of StdThreadStrategy. You also need to be aware that recycling a single instance like that is going to end up in race conditions. Just go and try what happens when you click the button again before pi had been fully computed.

Upvotes: 1

ptr
ptr

Reputation: 316

You could send a custom QEvent that carries the result from the worker thread to the thread your QObject lives in. See QEvent and QCoreApplication::postEvent()

Create your event class:

class MyEvent : public QEvent
{
public:
    MyEvent(double result) : QEvent(QEvent::User), mResult(result) {
    }

    double result() {
        return mResult;
    }

private:
    double mResult;
};

Post the event to the event loop and override the event() function to catch it:

class StdThreadStrategy : public QObject {
...
    void doTheWork() {
        double pi = pi_sum();
        QCoreApplication::postEvent(this, new MyEvent(pi));
    }
...
    bool event(QEvent *event) override {
        if (event->type() == QEvent::User) {
            const auto result = static_cast<MyEvent*>(event)->result();
            dialog->setResult(result);
        }
        return QObject::event(event);
    }
...
}

Upvotes: 1

nop666
nop666

Reputation: 603

There is no general answer to this kind of design problems. I give you just some tips here to show that c++11 provides a higher level of abstraction that can ease the manipulation of thread lifetime.

In your main GUI thread, run the async task (here I use std::async what gives you a std::future for further manipulation)

auto fut = std::async(std::launch::async, [=]() {
            // do some work
        });

Now, your UI is alive, run a Qtimer in the main thread with a callback that will check for the async procedure.

// callback content will be something like this
// checking via the future the state of the async task
if (fut.wait_for(std::chrono::milliseconds(25)) !=
                           std::future_status::ready) {
   // not yet finished
   return;
}

// No do what you want with the result
auto res = fut.get();
// emit a signal to refresh the gui etc.

Regards.

Upvotes: 2

Related Questions