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