如何实现频繁启动/停止线程(QThread)

5
我需要经常使用按钮启动和停止线程。我正在使用Qt。最近我学会了创建工作者的QObject并将其移动到QThread对象中,这是在Qt中实现线程的正确方法。以下是我的实现...

Worker.h

class worker : public QObject
{
    Q_OBJECT
public:
    explicit worker(QObject *parent = 0);
    void StopWork();
    void StartWork();
    bool IsWorkRunning();

signal:
    void SignalToObj_mainThreadGUI();

public slots:
    void do_Work();

private:
    void Sleep();

    volatile bool running,stopped;
    QMutex mutex;
    QWaitCondition waitcondition;
};

Worker.cpp

 worker::worker(QObject *parent) :
    QObject(parent),stopped(false),running(false)
{
}

void worker::do_Work()
{
    running = true;
    while(!stopped)
    {
        emit SignalToObj_mainThreadGUI();
        Sleep();
    }
}

void worker::Sleep()
{
    mutex.lock();
    waitcondition.wait(&mutex,10);
    mutex.unlock();
}

void worker::StopWork()
{
    mutex.lock();
    stopped = true;
    running = false;
    mutex.unlock();
}

void worker::StartWork()
{
    mutex.lock();
    stopped = false;
    running = true;
    mutex.unlock();
}

bool worker::IsWorkRunning()
{
    return running;
}

MainWindow.h

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:

private slots:
    void on_pushButton_push_to_start_clicked();

    void on_pushButton_push_to_stop_clicked();

private:

    Ui::MainWindow *ui;
    worker *myWorker;
    QThread *WorkerThread;
};

MainWindow.cpp

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myWorker = new worker;
    WorkerThread = new QThread;
    myWorker.moveToThread(WorkerThread);

    QObject::connect(WorkerThread,SIGNAL(started()),myWorker,SLOT(do_Work()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_push_to_start_clicked()
{
    if(!myWorker.IsWorkRunning())
       {
         myWorker->StartWork();
         WorkerThread->start();
       }
}

void MainWindow::on_pushButton_push_to_stop_clicked()
{
  if(myWorker.IsWorkRunning())
     {
       myWorker->StopWork();
       WorkerThread->quit();
     }
}

最初应用程序运行良好,但在按下按钮多次以启动和关闭线程的操作后,会出现以下错误...

QObject::killTimers(): timers cannot be stopped from another thread

我对这个错误感到困惑...我需要将启动/停止函数实现为信号和槽而不是类的成员函数吗?


3
在线程中,尽量不要调用start()和quit()。一旦将工作对象移动到线程中,应直接控制该工作对象。start()和quit()只用于启动和关闭线程。 - Ariya Hidayat
1个回答

8
不要启动和停止`WorkerThread`,只需让它保持运行即可。此外,将你的`StartWork()`和`StopWork()`方法移动到公共插槽部分。你真的不需要互斥量。 Worker.h
class worker : public QObject
{
  Q_OBJECT
public:
  explicit worker(QObject *parent = 0);

signal:
  void SignalToObj_mainThreadGUI();
  void running();
  void stopped();

public slots:
  void StopWork();
  void StartWork();

private slots:
  void do_Work();

private:
  volatile bool running,stopped;
};

Worker.cpp

worker::worker(QObject *parent) :
  QObject(parent), stopped(false), running(false)
{}

void worker::do_Work()
{
  emit SignalToObj_mainThreadGUI();

  if ( !running || stopped ) return;

  // do important work here

  // allow the thread's event loop to process other events before doing more "work"
  // for instance, your start/stop signals from the MainWindow
  QMetaObject::invokeMethod( this, "do_Work", Qt::QueuedConnection );
}

void worker::StopWork()
{
  stopped = true;
  running = false;
  emit stopped();
}

void worker::StartWork()
{
  stopped = false;
  running = true;
  emit running();
  do_Work();
}

MainWindow.h

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
  Q_OBJECT
public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow();

signals:
  void startWork();
  void stopWork();

private slots:
  void on_pushButton_push_to_start_clicked();
  void on_pushButton_push_to_stop_clicked();

private:
  Ui::MainWindow *ui;
  worker *myWorker;
  QThread *WorkerThread;

};

MainWindow.cpp

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
{
  ui->setupUi(this);

  myWorker = new worker;
  WorkerThread = new QThread;
  myWorker.moveToThread(WorkerThread);

  connect( this, SIGNAL(startWork()), myWorker, SLOT(StartWork()) );
  connect( this, SIGNAL(stopWork()), myWorker, SLOT(StopWork()) );
}

void MainWindow::on_pushButton_push_to_start_clicked()
{
  emit startWork();
}

void MainWindow::on_pushButton_push_to_stop_clicked()
{
  emit stopWork();
}

非常好的答案,谢谢!不过还有一个小问题,假设你有一个名为do_Work的函数,它是while(loop)的形式,那么如何中断该操作呢?我注意到QueuedConnection在处理时不会停止循环,所以我想我必须自己进行检查? - Dariusz
@Dariusz:在这种情况下,您不需要使用while循环。 do_Work会持续(异步)被调用,直到切换了runningstopped的状态。因此,将while循环体放入do_Work方法中,并使用标志(如答案中所示)在需要时退出“循环”。 - Jacob Robbins
我们需要为 runningstopped 使用 volatile 限定符吗? - Drobot Viktor
1
@DrobotViktor,不需要使用volatile。我只是从问题中复制/粘贴,并没有想到将其删除。我通过排队的信号/槽连接启动/停止工作线程,因此访问由WorkerThread事件循环序列化。 - Jacob Robbins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接