Как я могу передать результат из std :: thread в основной поток Gui в Qt?

#c #multithreading #qt

#c #многопоточность #qt

Вопрос:

Чтобы узнать о потоковой обработке в Qt и C , я создаю небольшой пример программы. У него есть графический интерфейс с кнопкой и текстовым полем:

введите описание изображения здесь

Когда пользователь нажимает кнопку Calculate, он вычисляет pi, используя формулу sum. Для того, чтобы графический интерфейс был отзывчивым во время этой длительной операции, вычисление будет выполняться в отдельном потоке.

Сначала я создал подкласс QThread , который выполняет вычисления в своем run() методе и выдает сигнал void resultReady(double value); , когда он завершен. Этот сигнал я подключил к слоту void setResult(double value); в моем Dialog классе Gui. Этот подход работает нормально.

Теперь я хочу сделать то же самое, используя std::thread . Как мне это сделать? У меня возникли проблемы с передачей результата обратно в графический интерфейс. Я попробовал это:

 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(amp;StdThreadStrategy::doTheWork, this);
        thread = std::move(t);
    }
private:
    Dialog* dialog;
    std::thread thread;
};
  

StdThreadStrategy Объект создается в конструкторе 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);
}
  

Я надеялся, что использование QTimer::singleShot в doTheWork() методе позволит мне отправлять сообщения в очередь событий графического интерфейса пользователя из другого потока.
К сожалению, я получаю сообщение об ошибке: QObject::startTimer: Timers can only be used with threads started with QThread .

Как я могу передать результат из std :: thread в основной поток Gui в Qt?

Комментарии:

1. Как вы запускаете свои потоки?

2. @vahancho: Я добавил еще несколько деталей.

Ответ №1:

Добавьте signal в свой экземпляр StdThreadStrategy и подключите его через явно отложенное соединение к обработчику, находящемуся в потоке пользовательского интерфейса. Таким образом, вы можете безопасно вызывать сигнал из любого потока, Qt позаботится об отправке его туда, куда он должен идти.

Также не забудьте включить join() свой поток в деструктор StdThreadStrategy . Вы также должны знать, что повторное использование одного экземпляра, подобного этому, закончится в условиях гонки. Просто зайдите и попробуйте, что произойдет, когда вы снова нажмете кнопку до того, как pi был полностью вычислен.

Комментарии:

1. Спасибо Ext3h за элегантное решение! В соответствии с вашими инструкциями я добавил connect(calculatePiStrategy, amp;StdThreadStrategy::valueCalculated, this, amp;Dialog::SetResult, Qt::ConnectionType::QueuedConnection); в конструктор Dialog, и это сработало нормально. Повторное нажатие кнопки до того, как pi был полностью вычислен, вызвало сбой в thread = std::move (t); таким образом, кажется, что переход к запущенным потокам не разрешен.

2. @Andy это не перемещение, которое запрещено, а тот факт, что вы должны join() уже запущенный поток, прежде чем вам разрешат забыть об этом.

Ответ №2:

Общего ответа на такого рода проблемы проектирования не существует. Здесь я даю вам всего лишь несколько советов, чтобы показать, что c 11 обеспечивает более высокий уровень абстракции, который может упростить управление временем жизни потока.

В вашем основном потоке GUI запустите задачу async (здесь я использую std::async, что дает вам std:: future для дальнейших манипуляций)

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

Теперь, когда ваш пользовательский интерфейс активен, запустите Qtimer в главном потоке с обратным вызовом, который проверит наличие асинхронной процедуры.

 // 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.
  

С уважением.

Ответ №3:

Вы могли бы отправить пользовательский QEvent, который переносит результат из рабочего потока в поток, в котором находится ваш QObject. Смотрите QEvent и QCoreApplication::postEvent()

Создайте свой класс событий:

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

    double result() {
        return mResu<
    }

private:
    double mResu<
};
  

Отправьте событие в цикл событий и переопределите функцию event (), чтобы перехватить его:

 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);
    }
...
}