【问题标题】:How to alter Qt Widgets in WINAPI threads?如何更改 WINAPI 线程中的 Qt 小部件?
【发布时间】:2020-08-27 20:32:48
【问题描述】:

我需要在三个 WINAPI 线程中同时更改三个进度条。由于我的功能是更改进度条的值以访问这些条,因此它们是 MainWindow 类的成员。所以我创建了带有CreateThread 函数所需签名的包装器,但是当我尝试启动我的线程时仍然遇到分段错误。它可能是由什么引起的,如何处理并让我的代码正常工作?

我的函数示例:

DWORD MainWindow::ThreadFunc1()
{
    for(int i = 0; i < 100; i++)
        {
            ui->progressBar->setValue(i);
            sleep(500);
        }
    return 0;
}

我的包装器示例

DWORD WINAPI MainWindow::threadStart1(LPVOID lpParams)
{
    MainWindow* This = (MainWindow*) lpParams;
    return This->ThreadFunc1();
}

【问题讨论】:

  • 据我所知,如果您使用的是 Qt,则不应访问本机线程库 - 使用 QThread 或任何其他足够的类 doc.qt.io/qt-5/threads-technologies.html 此外,请注意直接访问不属于您的 QObjects线程。
  • 实际问题是 Qt GUI 不是线程安全的(与您使用 QThreadstd::thread 甚至本机 API 无关)。一个非常简单的解决方案可能是使用事件或信号(也提供线程安全通信)发送更新(从线程到 GUI)。更简单的是让您的线程在您的 Qt GUI 应用程序时更新全局或共享原子变量。使用QTimer 进行定期更新。
  • 这不是和asked earlier一样的问题吗?那里提供的答案有什么问题?

标签: c++ multithreading qt winapi qtwidgets


【解决方案1】:

我不知道 OP 已经问了一个非常相似的问题

SO: Problem with using WINAPI threads in Qt in C++

(在撰写本文时)已经有两个有价值的答案。

尽管如此,我还是发布了我在注意到上述内容之前完成的示例。


多线程通常需要额外注意线程间通信。与多处理相反(默认情况下,每个进程都有未共享的变量,并且需要对Inter-Process Communication 进行特殊处理),所有线程都可以访问相同的进程变量。只要每个线程在其生命周期内专门使用其变量,一切都很好。一旦至少一个线程读取了一个被另一个线程修改的变量,麻烦就开始了。为了防止data races and race-conditions,需要人工作者负责的线程同步。忽略这一点(即使是无意的)会引入Undefined Behavior,在这种情况下,编译器不会为此提供任何有用的诊断。

Qt GUI 不适用于多线程。因此,小部件和小部件属性不是线程安全的。

除此之外,Qt 还为多线程做好了准备。

  1. QObject 准备具有线程亲和性。 →Thread Affinity

  2. Qt 信号可用于线程间通信。为此,QObject::connect() 的所有相关风格都提供了Qt::ConnectionType 的参数。当一个 Qt 信号连接到另一个QObject 的插槽时,默认情况下,发送者和接收者对象的线程亲和性将用于适当调整连接类型:

    如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。

  3. 如果非 GUI 线程调用 GUI 对象的成员函数,这肯定会导致未定义行为(由于缺乏线程安全性)。相反,线程可以向 GUI 线程的事件循环添加请求,以便 GUI 线程可以按顺序处理并同步。为此,可以使用QApplication::postEvent()。显然,这是罕见的 Qt GUI 函数之一,它被明确标记为 thread-safe

Thread Support in Qt 中给出了更全面的概述

  1. 另一个选择是使用std:: C++ 多线程工具,如std::thread 来生成和加入线程,std::mutex(和公司)保护共享访问,和/或std::atomic 用于(可能) 无锁的线程间通信。

这样做的好处是可以使用(可能已经存在的)线程安全代码,它专门基于std:: C++ 库的东西。 Qt GUI 的(定期)更新由QTimer 管理,这是一种轮询,但会导致简单的刷新率管理,而不会有其他线程开始淹没 GUI 线程的事件循环的危险。 (过于频繁地更新 Qt GUI 可能会导致相当大的性能影响,并显着降低其反应性。)

关于选项 4,我曾经为另一个类似的问题写了一个答案 SO: Qt C++ Displaying images outside the GUI thread (Boost thread)

回想起来,我为 OP 的问题写了一个新的示例:

C++源码testQProgressMultiThreading.cc

// standard C++ header:
#include <atomic>
#include <chrono>
#include <thread>

// Qt header:
#include <QtWidgets>

// a wrapper for a thread with some added context
struct Worker {
  const uint id; // constant over thread runtime - no sync. needed
  std::thread thread; // the thread instance
  std::atomic<uint> progress; // shared data (written in worker, read by UI)
  std::atomic<bool> exit; // flag to signal abort (from UI to worker)

  Worker(uint id): id(id), progress(0), exit(true) { }
  ~Worker() { if (thread.joinable()) thread.join(); }

  void start()
  {
    if (thread.joinable()) return; // worker already working
    qDebug() << "Start worker " << id;
    progress = 0; exit = false;
    thread = std::thread(&Worker::work, this);
  }

  void stop()
  {
    if (!thread.joinable()) return; // worker not working
    exit = true;
    thread.join();
    progress = 0;
    qDebug() << "Worker" << id << "finished.";
  }

  void work()
  {
    qDebug() << "Enter worker " << id;
    while (progress < 100 && !exit) {
      // consume some time (without heating the CPU too much)
      std::this_thread::sleep_for(std::chrono::milliseconds(50));
      // confirm some work progress
      ++progress;
    }
    qDebug() << "Leaving worker " << id;
  }

  bool working() const { return thread.joinable(); }
};

// main application
int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // workers
  Worker worker1(1), worker2(2), worker3(3);
  // setup GUI
  QWidget qWinMain;
  qWinMain.setWindowTitle("Test QProgress Multi-Threading");
  qWinMain.resize(640, 480);
  QVBoxLayout qVBox;
  QHBoxLayout qHBox;
  qHBox.addStretch();
  QPushButton qBtnStart1("Start 1");
  qHBox.addWidget(&qBtnStart1);
  QPushButton qBtnStart2("Start 2");
  qHBox.addWidget(&qBtnStart2);
  QPushButton qBtnStart3("Start 3");
  qHBox.addWidget(&qBtnStart3);
  QPushButton qBtnStop("Stop");
  qHBox.addWidget(&qBtnStop);
  qVBox.addLayout(&qHBox);
  QProgressBar qProgress1;
  qVBox.addWidget(&qProgress1);
  QProgressBar qProgress2;
  qVBox.addWidget(&qProgress2);
  QProgressBar qProgress3;
  qVBox.addWidget(&qProgress3);
  qWinMain.setLayout(&qVBox);
  qWinMain.show();
  // prepare timer
  QTimer qTimerProgress;
  qTimerProgress.setInterval(50); // update rate for GUI 50 ms -> 20 Hz (round about)
  // install signal-handlers
  QObject::connect(&qBtnStart1, &QPushButton::clicked,
    [&](bool) {
      worker1.start();
      if (!qTimerProgress.isActive()) qTimerProgress.start();
    });
  QObject::connect(&qBtnStart2, &QPushButton::clicked,
    [&](bool) {
      worker2.start();
      if (!qTimerProgress.isActive()) qTimerProgress.start();
    });
  QObject::connect(&qBtnStart3, &QPushButton::clicked,
    [&](bool) {
      worker3.start();
      if (!qTimerProgress.isActive()) qTimerProgress.start();
    });
  QObject::connect(&qBtnStop, &QPushButton::clicked,
    [&](bool) {
      worker1.stop(); qProgress1.setValue(worker1.progress);
      worker2.stop(); qProgress2.setValue(worker2.progress);
      worker3.stop(); qProgress3.setValue(worker3.progress);
      qTimerProgress.stop();
    });
  QObject::connect(&qTimerProgress, &QTimer::timeout,
    [&](){
      qProgress1.setValue(worker1.progress);
      if (worker1.progress >= 100) worker1.stop();
      qProgress2.setValue(worker2.progress);
      if (worker2.progress >= 100) worker2.stop();
      qProgress3.setValue(worker3.progress);
      if (worker3.progress >= 100) worker3.stop();
      if (!worker1.working() && !worker2.working() && !worker3.working()) {
        qTimerProgress.stop();
      }
    });
  // runtime loop
  return app.exec();
}

一个CMake build-script CMakeLists.txt 准备一个VS解决方案:

project(QProgressMultiThreading)

cmake_minimum_required(VERSION 3.10.0)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(Qt5Widgets CONFIG REQUIRED)

include_directories("${CMAKE_SOURCE_DIR}")

add_executable(testQProgressMultiThreading testQProgressMultiThreading.cc)

target_link_libraries(testQProgressMultiThreading Qt5::Widgets)

在 VS2017(Windows 10、Qt 5.13)中构建和测试:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    相关资源
    最近更新 更多