簡體   English   中英

QThread::exit 或 QThread::quit 不會終止由 derived class 創建的線程

[英]QThread::exit or QThread::quit won't terminate the thread created by derived class

我認為這是一個非常簡單的問題。 但是嘗試了幾次之后,我發現我花了比預期更多的時間。 所以我來這里是為了你的幫助。

我的環境

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

主.cpp

#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)
 {

 }
 // ...

我錯過了什么? 提前致謝。

您正在調用方法quit()但文檔聲明:

如果線程沒有事件循環,這個 function 什么都不做。

我引用你的代碼(我還不能編輯你的帖子)

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

您上面的代碼沒有任何退出線程的操作 - id 只是一個無限循環。

這是代碼 - 沒有魔法 - 它不會自行完成

要退出線程,您有很多選擇,例如:

  • 使用 class QThreadrequestInterruption()isInterruptionRequested()方法對
  • 檢查一些局部變量 - 例如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();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM