0
//Work.h
#ifndef WORK_H
#define WORK_H

#include <QDebug>
#include <QObject>
#include <QThread>

class Work : public QObject {
  Q_OBJECT
 public:
  explicit Work(QObject *parent = nullptr);

 public slots:
  void snap();
  void setStatus();

 signals:

 private:
  bool status;
};

#endif  // WORK_H


//Work.cpp
#include "Work.h"

Work::Work(QObject *parent) : QObject(parent) { status = true; }

void Work::snap() {
  status = true;
  while (true) {
    if (status) {
      qDebug() << "Work thread: " << QThread::currentThreadId();
    } else {
      qDebug() << "STOP";
      break;
    }
  }
}

void Work::setStatus() { status = false; }
//MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

#include "Work.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
  Q_OBJECT

 public:
  MainWindow(QWidget *parent = nullptr);
  ~MainWindow();

 private:
  Ui::MainWindow *ui;
  Work *work;
  QThread thread;
};
#endif  // MAINWINDOW_H


//MainWindow.cpp
#include "MainWindow.h"

#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
  ui->setupUi(this);
  work = new Work();
  work->moveToThread(&thread);
  thread.start();

  connect(ui->startButton, SIGNAL(clicked()), work, SLOT(snap()));
  connect(ui->stopButton, SIGNAL(clicked()), work, SLOT(setStatus()));
}

MainWindow::~MainWindow() {
  thread.terminate();
  delete ui;
}
//main.cpp
#include <QApplication>

#include "MainWindow.h"

int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
  MainWindow w;
  qDebug() << QThread::currentThreadId();
  w.show();
  return a.exec();
}

I use MainWindow to display, Work to do something. And I use work->moveToThread(&thread).

Click start button to execute snap function in Work, what I want to do is when I click stop button, the snap function output STOP. And I can still start and stop whenever I like.

But I fail. It seems impossible to change the status during the while loop. Work doesn't get the stopButton clicked signal. Is it because of priority?

Could anyone give me some advices?

liuyulvv
  • 154
  • 10
  • 2
    Please edit your question to provide a [mcve] that can be used to reproduce the problem. – G.M. Dec 10 '20 at 13:10

2 Answers2

0

Firstly consider your Work::snap implementation...

void Work::snap() {
  status = true;
  while (true) {
    if (status) {
      qDebug() << "Work thread: " << QThread::currentThreadId();
    } else {
      qDebug() << "STOP";
      break;
    }
  }
}

Once started it never yields control to the Qt event loop. Now consider the connect call...

connect(ui->stopButton, SIGNAL(clicked()), work, SLOT(setStatus()));

Since ui->stopButton and work have different thread affinities this is effectively a queued connection and requires the receiver to have an active event loop. Hence the call to setStatus will remain pending forever.

A better way to achieve your goal might be to make use of a simple atomic bool...

std::atomic<bool> status;

and change the connect call to modify status directly using a lambda (untested)...

connect(ui->stopButton, &QPushButton::clicked, [this]{ work->setStatus(); });
liuyulvv
  • 154
  • 10
G.M.
  • 12,232
  • 2
  • 15
  • 18
  • And I retry your code and there is a little errors: `connect(ui->stopButton, &QPushButton::clicked, [work]{ work->setStatus(); });` I change it to :`connect(ui->stopButton, &QPushButton::clicked, [this] { work->setStatus(); });` After I change `work` to `this` ,your answer is right. [link](https://stackoverflow.com/questions/7895879/using-member-variable-in-lambda-capture-list-inside-a-member-function) – liuyulvv Dec 12 '20 at 13:26
  • @liuyulvv Indeed, my mistake. Thanks for the edit. – G.M. Dec 12 '20 at 13:33
0

I solve it.

I add a slot and a signal in MainWindow and change stop slot.

connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::start);
connect(this, &MainWindow::startSnap, work, &Work::snap);

// start slot
void MainWindow::start() {
  thread.start();
  emit startSnap();
}

void MainWindow::stop() {
  if (thread.isRunning()) {
    thread.requestInterruption();
  }
}

And change the codes in Work::snap

void Work::snap() {
  while (true) {
    if (QThread::currentThread()->isInterruptionRequested()) {
      qDebug() << "STOP";
      QThread::currentThread()->exit();
      return;
    } else {
      qDebug() << "Work thread: " << QThread::currentThreadId();
    }
  }
}

The key codes are:

  • thread.requestInterruption();(MainWindow::stop)
  • QThread::currentThread()->exit();(Work::snap)
  • thread.start();(MainWindow::start)
liuyulvv
  • 154
  • 10
  • How is this different from my own answer? You make use of `requestInterruption` whereas I used an atomic bool to request interruption. It should amount to the same thing. Can you show how `MainWindow::stop` is called? The `connect` call? – G.M. Dec 12 '20 at 13:09
  • `connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stop);` – liuyulvv Dec 12 '20 at 13:25