How to alter Qt Widgets in WINAPI threads?

前端 未结 1 1710
醉酒成梦
醉酒成梦 2020-12-22 14:22

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

相关标签:
1条回答
  • 2020-12-22 15:20

    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):

    0 讨论(0)
提交回复
热议问题