如何在 WINAPI 线程中更改 Qt 小部件? [英] How to alter Qt Widgets in WINAPI threads?

查看:28
本文介绍了如何在 WINAPI 线程中更改 Qt 小部件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我的函数示例:

DWORD MainWindow::ThreadFunc1(){for(int i = 0; i <100; i++){ui->progressBar->setValue(i);睡眠(500);}返回0;}

我的包装器示例

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

解决方案

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

I need to alter three progress bars simultaneously in three WINAPI threads. Since my funtions, that change value of progress bars are to have access to these bars, they are members of MainWindow class. So i created wrappers with signature that is required by CreateThread function, but I still get segmentation fault when I try to start my threads. What it can be caused by and how to handle this and get my code to work?

Example of my function:

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

Example of my wrapper

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

解决方案

I was not aware that OP already asked a very similar question

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

which has (at the time of this writing) already two valuable answers.

Nevertheless, I publish my sample which I had complete before noticing the above.


Multi-threading usually requires additional care for the interthread communication. In opposition to multi-processing (where each process has unshared variables by default and requires special effort for Inter-Process Communication), all threads may access the same process variables. As long as each thread uses its variables exclusively during its life-time, everything is fine. As soon as at least one thread reads a variable which is modified by another thread, the trouble starts. To prevent data races and race-conditions, a thread synchronization is needed for which the human author is responsible. Ignoring this (even unintendedly) introduces Undefined Behavior, and, in such cases, the compiler doesn't contribute any helpful diagnosis for this.

The Qt GUI is not intended for multi-threading. Hence, the widgets and widget properties are not thread-safe.

Beside of this, Qt is prepared for multi-threading.

  1. QObject is prepared to have a thread affinity. → Thread Affinity

  2. Qt signals can be used for interthread communication. For this, all relevant flavors of QObject::connect() provide a parameter of Qt::ConnectionType. When a Qt signal is connected to a slot of another QObject, by default, the thread affinities of sender and receiver object will be used to adjust the connection type appropriately:

    If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.

  3. If a non-GUI thread calls member functions of GUI objects, this results surely in Undefined Behavior (due to the lack of thread-safety). Instead, the thread may add a request to the event loop of the GUI thread so that the GUI thread can process it in order and sync. For this, QApplication::postEvent() can be used. Apparently, this is one of the rare Qt GUI functions which is explicitly remarked as thread-safe.

A more encompassing overview is given in Thread Support in Qt

  1. Another option is the usage of the std:: C++ multi-threading tools like std::thread to spawn and join threads, std::mutex (and company) to guard shared accesses, and/or std::atomic for a (potentially) lock-free interthread communication.

This has the advantage that (maybe already existing) thread-safe code can be used which is based on std:: C++ library stuff exclusively. The (periodical) update of Qt GUI is managed by a QTimer which is some kind of polling but results in a simple refresh-rate management without the danger that other threads start to flood the event loop of the GUI thread. (Updating the Qt GUI too frequently may result in a considerable performance impact with a noticable drop of its reactivity.)

About option 4, I once wrote an answer SO: Qt C++ Displaying images outside the GUI thread (Boost thread) for yet another similar question.

Recalling this, I wrote a new sample for the question of OP:

The C++ source 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();
}

A CMake build-script CMakeLists.txt to prepare a VS solution:

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)

Built and tested in VS2017 (Windows 10, Qt 5.13):

这篇关于如何在 WINAPI 线程中更改 Qt 小部件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆