gpt4 book ai didi

c++ - QtConcurrent-在数千个发布到UI线程的结果中保持GUI响应

转载 作者:行者123 更新时间:2023-12-02 10:15:26 31 4
gpt4 key购买 nike

我有一个应用程序,它可能会长时间运行任务,也可能有成千上万个结果。

这个特定的应用程序(下面的代码)没有任何值(value),但是它旨在提供一个通用的用例,即在成千上万的结果中保持响应式UI的需求。

需要明确的是,我知道应该减少对UI进行轮询的次数。我的问题是关于可用于保持响应式UI的设计原理(可应用于此(和其他类似)方案)。

我的第一个想法是使用QTimer并在例如200ms,一个可以在here中找到但需要注意的示例。

哪些方法可用,哪些方法可以保持响应式UI?

我尝试解释的一个简单示例如下。我有一个UI:

  • 生成一个整数列表
  • 将其传递到映射函数以pow(x,2)值,然后
  • 测量进度

  • 运行此应用程序时,单击“开始”按钮将运行该应用程序,但是由于 QueuedConnection: QFutureWatcher::resultReadyAt处理结果的频率较高,因此UI无法响应任何用户的单击,因此尝试“暂停”或“停止” (取消)是徒劳的。

    用于传递lambda的QtConcurrent::mapped()函数的包装器(用于成员函数)
    #include <functional>

    template <typename ResultType>
    class MappedFutureWrapper
    {
    public:
    using result_type = ResultType;

    MappedFutureWrapper<ResultType>(){}
    MappedFutureWrapper<ResultType>(std::function<ResultType (ResultType)> function): function(function){ }
    MappedFutureWrapper& operator =(const MappedFutureWrapper &wrapper) {
    function = wrapper.function;
    return *this;
    }
    ResultType operator()(ResultType i) {
    return function(i);
    }

    private:
    std::function<ResultType(ResultType)> function;
    };

    MainWindow.h用户界面
    class MainWindow : public QMainWindow {
    Q_OBJECT

    public:
    struct IntStream {
    int value;
    };

    MappedFutureWrapper<IntStream> wrapper;
    QVector<IntStream> intList;

    int count = 0;
    int entries = 50000000;

    MainWindow(QWidget* parent = nullptr);
    static IntStream doubleValue(IntStream &i);
    ~MainWindow();

    private:
    Ui::MainWindow* ui;
    QFutureWatcher<IntStream> futureWatcher;
    QFuture<IntStream> future;

    //...
    }

    MainWindow实现
    MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    qDebug() << "Launching";

    intList = QVector<IntStream>();
    for (int i = 0; i < entries; i++) {
    int localQrand = qrand();
    IntStream s;
    s.value = localQrand;
    intList.append(s);
    }

    ui->progressBar->setValue(0);

    }

    MainWindow::IntStream MainWindow::doubleValue(MainWindow::IntStream &i)
    {
    i.value *= i.value;
    return i;
    }

    void MainWindow::on_thread1Start_clicked()
    {
    qDebug() << "Starting";

    // Create wrapper with member function
    wrapper = MappedFutureWrapper<IntStream>([this](IntStream i){
    return this->doubleValue(i);
    });

    // Process 'result', need to acquire manually
    connect(&futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [this](int index){
    auto p = ((++count * 1.0) / entries * 1.0) * 100;
    int progress = static_cast<int>(p);
    if(this->ui->progressBar->value() != progress) {
    qDebug() << "Progress = " << progress;
    this->ui->progressBar->setValue(progress);
    }
    });

    // On future finished
    connect(&futureWatcher, &QFutureWatcher<IntStream>::finished, this, [](){
    qDebug() << "done";
    });

    // Start mapped function
    future = QtConcurrent::mapped(intList, wrapper);
    futureWatcher.setFuture(future);
    }

    void MainWindow::on_thread1PauseResume_clicked()
    {
    future.togglePaused();
    if(future.isPaused()) {
    qDebug() << "Paused";
    } else {
    qDebug() << "Running";
    }
    }

    void MainWindow::on_thread1Stop_clicked()
    {
    future.cancel();
    qDebug() << "Canceled";

    if(future.isFinished()){
    qDebug() << "Finished";
    } else {
    qDebug() << "Not finished";
    }

    }

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

    说明UI为何“未响应”。

    除了打印“启动”之外,UI加载时不执行任何其他操作。调用 on_thread1Start_clicked()方法时,除了添加以下连接之外,它还启动了将来的操作:
    connect(&futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [this](int index){
    auto p = ((++count * 1.0) / entries * 1.0) * 100;
    int progress = static_cast<int>(p);
    if(this->ui->progressBar->value() != progress) {
    qDebug() << "Progress = " << progress;
    this->ui->progressBar->setValue(progress);
    }
    });

    该连接监听将来的结果,并对其进行操作(此连接函数在 UI线程上运行)。由于我正在仿真 int entries = 50000000;所示的大量“ui更新”,因此每次处理结果时,都会调用 QFutureWatcher<IntStream>::resultReadyAt

    当此命令运行+/- 2秒时,UI不会响应分别链接到 on_thread1PauseResume_clicked()on_thread1Stop_clicked的“暂停”或“停止”点击。

    最佳答案

    您使用QtConcurrent::mapped的方法非常合理,我认为从理论上讲,这可能是解决此类问题的好方法。这里的问题是,添加到事件队列中的事件数量太多,无法保持UI的响应速度。

    UI不响应的原因是,GUI线程中只有一个事件队列。结果,您的按钮clicked事件与resultReadyAt事件一起排队。但是队列只是一个队列,因此,如果您的按钮事件在resultReadyAt事件说30'000'000之后进入队列,则仅在轮到该事件时才对其进行处理。 resizemove事件也是如此。结果,UI感觉迟钝并且没有响应。

    一种可能是修改您的映射功能,以便接收单个数据块而不是单个数据点。例如,我将50000数据拆分为1000批次的50000数据。您可以看到,在这种情况下,UI在所有执行过程中都是响应式的。我还在每个函数中添加了20ms的延迟,否则执行速度如此之快,以至于我什至无法按下停止/暂停按钮。

    您的代码也有一些小注释:

  • 原则上,您不需要包装器类,因为您可以直接传递成员函数(再次参见下面的第一个示例)。如果您有问题,可能与您使用的Qt版本或编译器有关。
  • 您实际上是在更改传递给doubleValue的值。实际上,这使从函数返回值变得毫无用处。
  • #include <QApplication>
    #include <QMainWindow>
    #include <QProgressBar>
    #include <QPushButton>
    #include <QRandomGenerator>
    #include <QtConcurrent>
    #include <QVBoxLayout>


    class Widget : public QWidget {
    Q_OBJECT

    public:
    struct IntStream {
    int value;
    };

    Widget(QWidget* parent = nullptr);
    static QVector<IntStream> doubleValue(const QVector<IntStream>& v);

    public slots:
    void startThread();
    void pauseResumeThread();
    void stopThread();

    private:
    static constexpr int BATCH_SIZE {50000};
    static constexpr int TOTAL_BATCHES {1000};
    QFutureWatcher<QVector<IntStream>> m_futureWatcher;
    QFuture<QVector<IntStream>> m_future;
    QProgressBar m_progressBar;
    QVector<QVector<IntStream>> m_intList;
    int m_count {0};
    };


    Widget::Widget(QWidget* parent) : QWidget(parent)
    {
    auto layout {new QVBoxLayout {}};

    auto pushButton_startThread {new QPushButton {"Start Thread"}};
    layout->addWidget(pushButton_startThread);
    connect(pushButton_startThread, &QPushButton::clicked,
    this, &Widget::startThread);

    auto pushButton_pauseResumeThread {new QPushButton {"Pause/Resume Thread"}};
    layout->addWidget(pushButton_pauseResumeThread);
    connect(pushButton_pauseResumeThread, &QPushButton::clicked,
    this, &Widget::pauseResumeThread);

    auto pushButton_stopThread {new QPushButton {"Stop Thread"}};
    layout->addWidget(pushButton_stopThread);
    connect(pushButton_stopThread, &QPushButton::clicked,
    this, &Widget::stopThread);

    layout->addWidget(&m_progressBar);

    setLayout(layout);

    qDebug() << "Launching";

    for (auto i {0}; i < TOTAL_BATCHES; i++) {
    QVector<IntStream> v;
    for (auto j {0}; j < BATCH_SIZE; ++j)
    v.append(IntStream {static_cast<int>(QRandomGenerator::global()->generate())});
    m_intList.append(v);
    }
    }

    QVector<Widget::IntStream> Widget::doubleValue(const QVector<IntStream>& v)
    {
    QThread::msleep(20);
    QVector<IntStream> out;
    for (const auto& x: v) {
    out.append(IntStream {x.value * x.value});
    }
    return out;
    }

    void Widget::startThread()
    {
    if (m_future.isRunning())
    return;
    qDebug() << "Starting";

    m_count = 0;

    connect(&m_futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [=](int){
    auto progress {static_cast<int>(++m_count * 100.0 / TOTAL_BATCHES)};
    if (m_progressBar.value() != progress && progress <= m_progressBar.maximum()) {
    m_progressBar.setValue(progress);
    }
    });

    connect(&m_futureWatcher, &QFutureWatcher<IntStream>::finished,
    [](){
    qDebug() << "Done";
    });

    m_future = QtConcurrent::mapped(m_intList, &Widget::doubleValue);
    m_futureWatcher.setFuture(m_future);
    }

    void Widget::pauseResumeThread()
    {
    m_future.togglePaused();

    if (m_future.isPaused())
    qDebug() << "Paused";
    else
    qDebug() << "Running";
    }

    void Widget::stopThread()
    {
    m_future.cancel();
    qDebug() << "Canceled";

    if (m_future.isFinished())
    qDebug() << "Finished";
    else
    qDebug() << "Not finished";
    }


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

    #include "main.moc"

    另一个真正好的替代方法是使用Jeremy Friesner建议的单独的工作线程。如果您愿意,我们也可以详细说明=)

    关于c++ - QtConcurrent-在数千个发布到UI线程的结果中保持GUI响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62144289/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com