首页 > 解决方案 > QThread::exit 或 QThread::quit 不会终止派生类创建的线程

问题描述

我认为这是一个非常简单的问题。但是尝试了几次之后,我意识到我花费的时间比预期的要多。所以我来这里寻求你的帮助。

我的环境

ubuntu 18.04
qt 5.15.0
cmake 3.18.1  # don't think it matters

我已将我的程序最小化为一个小程序。这很简单。有两个按钮,一个启动按钮和一个停止按钮,用于启动和停止由 QThread 派生的 SubThread 实例创建的线程。我在最小程序中上传了我的代码。

要重现该问题,请从 QCreator 启动小程序,然后按下启动按钮。SubThread 线程按预期打印消息。但是然后按下停止按钮,该线程不会退出,这是出乎意料的。我停在这里。

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(miniprogram LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# QtCreator supports the following variables for Android, which are identical to qmake Android variables.
# Check http://doc.qt.io/qt-5/deployment-android.html for more information.
# They need to be set before the find_package(Qt5 ...) call.

#if(ANDROID)
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
#    if (ANDROID_ABI STREQUAL "armeabi-v7a")
#        set(ANDROID_EXTRA_LIBS
#            ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so
#            ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so)
#    endif()
#endif()

find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)

if(ANDROID)
  add_library(miniprogram SHARED
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    subthread.cpp
    subthread.h
  )
else()
  add_executable(miniprogram
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    subthread.cpp
    subthread.h
  )
endif()

target_link_libraries(miniprogram PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "subthread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
  Q_OBJECT

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

private slots:
  void on_pushButton_clicked();

  void on_pushButton_2_clicked();

private:
  Ui::MainWindow *ui;
  SubThread *subThread;
};
#endif // MAINWINDOW_H

子线程.h

#ifndef SUBTHREAD_H
#define SUBTHREAD_H

#include <QThread>

class SubThread : public QThread
{
public:
  SubThread();

protected:
  void run() override;
};

#endif // SUBTHREAD_H

主文件

#include "mainwindow.h"

#include <QApplication>

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

主窗口.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "subthread.h"

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

  subThread = new SubThread;
}

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


void MainWindow::on_pushButton_clicked()
{
  qInfo("Start subthread from mainwindows");
  subThread->start();
}


void MainWindow::on_pushButton_2_clicked()
{
  qInfo("Stop subthread from mainwindows");
  subThread->quit();
}

子线程.cpp

#include "subthread.h"

#include <iostream>

SubThread::SubThread()
{

}
// protected member
void SubThread::run()
{
  qInfo("Thread running...");
  while(true)
  {
    usleep(300000);  // 300ms
    std::cout << "." << std::flush;
  }
  qInfo("Thread exists...");
}

主窗口.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>487</width>
    <height>333</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>200</x>
      <y>140</y>
      <width>89</width>
      <height>25</height>
     </rect>
    </property>
    <property name="text">
     <string>Start</string>
    </property>
   </widget>
   <widget class="QPushButton" name="pushButton_2">
    <property name="geometry">
     <rect>
      <x>350</x>
      <y>140</y>
      <width>89</width>
      <height>25</height>
     </rect>
    </property>
    <property name="text">
     <string>Stop</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>487</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

[更新]

在我阅读了 QEventLoop 文档后,我发现这不是我想要的。所以我在那个while循环中使用了通信标志,比如

 // ...
 while(!quitflag)
 {

 }
 // ...

我错过了什么?提前致谢。

标签: c++qt5

解决方案


您正在调用方法quit()但文档声明:

如果线程没有事件循环,则此函数不执行任何操作。

我引用了你的代码(我还不能编辑你的帖子)

void SubThread::run()
{
  qInfo("Thread running...");
  while(true)
  {
    usleep(300000);  // 300ms
    std::cout << "." << std::flush;
  }
  qInfo("Thread exists...");
}

您上面的代码没有任何退出线程的操作 - id 只是一个无限循环。

这是代码 - 没有魔法 - 它不会自行完成

要退出线程,您有很多选择,例如:

  • 类的使用requestInterruption()isInterruptionRequested()方法对QThread
  • 检查一些局部变量 - 例如quit_requested- 并在请求时完成无限循环(像这样while(quit_requested)) - 并在按钮处理程序中设置该变量(当然通过访问方法)
  • 使用一些线程间通信
  • 根据文档使用事件循环

示例(第一个选项):

void SubThread::run()
{
  qInfo("Thread running...");
  while(!QThread::currentThread()->isInterruptionRequested())
  {
    usleep(300000);  // 300ms
    std::cout << "." << std::flush;
  }
  qInfo("Thread exists...");
}

// finish thread from button handler
void MainWindow::on_pushButton_2_clicked()
{
  qInfo("Stop subthread from mainwindows");
  subThread->requestInterruption();
}

推荐阅读