C++ QtConcurrent-在发布到UI线程的数千个结果中保持GUI响应
我有一个应用程序,它可能有长时间运行的任务,也可能有数千或数百万个任务或结果 这个特定的应用程序(下面的代码)没有任何价值,但它的目的是提供一个通用用例,说明需要在“数千”个结果中维护一个响应良好的UI 要明确的是,我知道应该减少用户界面的轮询次数。我的问题是关于可以应用于此(和其他类似)场景以保持响应性UI的设计原则 我的第一个想法是使用a,每200毫秒处理一次所有“结果”,这是一个可以找到但需要改进的例子 哪些方法可用,哪些方法更适合保持响应性用户界面?C++ QtConcurrent-在发布到UI线程的数千个结果中保持GUI响应,c++,multithreading,qt,qtconcurrent,qfuture,C++,Multithreading,Qt,Qtconcurrent,Qfuture,我有一个应用程序,它可能有长时间运行的任务,也可能有数千或数百万个任务或结果 这个特定的应用程序(下面的代码)没有任何价值,但它的目的是提供一个通用用例,说明需要在“数千”个结果中维护一个响应良好的UI 要明确的是,我知道应该减少用户界面的轮询次数。我的问题是关于可以应用于此(和其他类似)场景以保持响应性UI的设计原则 我的第一个想法是使用a,每200毫秒处理一次所有“结果”,这是一个可以找到但需要改进的例子 哪些方法可用,哪些方法更适合保持响应性用户界面? 下面是我试图解释的一个简单例子。我
下面是我试图解释的一个简单例子。我有一个用户界面:
QtConcurrent::mapped()
传递lambda的函数(用于成员函数)
此连接侦听来自未来的结果,并对其进行操作(此连接函数在UI线程上运行)。因为我正在模拟大量的“ui更新”,显示为int entries=50000000
,每次处理结果时,都会调用QFutureWatcher::resultradayat
运行+/-2秒时,UI不会对链接到线程1暂停上的
和线程1停止上的的“暂停”或“停止”单击作出响应。分别单击和。使用QtConcurrent::mapped
的方法非常合理,我认为从理论上讲,这可能是解决这个问题的一个好办法。这里的问题是,添加到事件队列的事件数量太多,无法使UI保持响应
UI没有响应的原因是GUI线程中只有一个事件队列。因此,您单击的按钮事件将与resultradayt
事件一起排队。但是队列只是一个队列,所以如果您的按钮事件在30'000'000的ResulteAYAT事件之后进入队列,那么只有轮到它时才会处理它。resize
和move
事件也是如此。因此,UI感觉迟钝,没有响应能力
一种可能是修改映射函数,以代替单个数据点接收数据块。例如,我将50000数据拆分为1000批50000数据。您可以看到,在这种情况下,UI在所有执行过程中都是响应的。我还在每个函数中添加了20ms延迟,否则执行速度太快,我甚至无法按下停止/暂停按钮
您的代码中还有几个小注释:
- 原则上,您不需要包装器类,因为您可以直接传递成员函数(再次参见下面的第一个示例)。如果您有问题,可能与您正在使用的Qt版本或编译器有关
- 实际上,您正在更改传递给
doubleValue
的值。这实际上使得从函数返回值变得无用
#包括
#包括
#包括
#包括
#包括
#包括
#包括
类Widget:publicqwidget{
Q_对象
公众:
结构IntStream{
int值;
};
小部件(QWidget*parent=nullptr);
静态QVector双值(常数QVector&v);
公众时段:
void startThread();
void pauseResumeThread();
void stop thread();
私人:
静态constexpr int批处理大小{50000};
静态constexpr int总计{1000};
QFutureWatcher m_futureWatcher;
未来,未来;
QProgressBar m_progressBar;
QVector m_intList;
int m_计数{0};
};
Widget::Widget(QWidget*parent):QWidget(parent)
{
自动布局{new QVBoxLayout{};
自动按钮_startThread{new QPushButton{“Start Thread”};
布局->添加小部件(按钮\u开始线程);
连接(按钮启动线程,&QPushButton::单击,
这是(Widget::startThread)(&Widget::startThread);
自动按钮_pauseResumeThread{new QPushButton{“暂停/恢复线程”};
布局->添加小部件(按钮_pauseResumeThread);
连接(按钮\u暂停刷新,&QPushButton::单击,
这是一个小部件(&Widget::pauseResumeThread);
自动按钮{u停止线程{new QPushButton{“停止线程”};
布局->添加小部件(按钮\停止线程);
连接(按钮\停止线程,&QPushButton::单击,
这是&Widget::stopThread);
布局->添加小部件(&m_progressBar);
设置布局(布局);
qDebug()生成())});
m_intList.append(v);
}
}
QVector小部件::doubleValue(constqvector&v)
{
QThread::msleep(20);
矢量输出;
用于(常数自动和x:v){
append(IntStream{x.value*x.value});
}
返回;
}
void小部件::startThread()
{
if(m_future.isRunning())
返回;
qDebug()也许你可以创建一个专门的结果处理线程,并将数百万个结果导入该线程。它可以对结果进行任何初始处理,并将结果汇总成“执行摘要”它将以更长的时间间隔移交给GUI线程。感谢您的回答-我认为对线程进行子类化或添加延迟(我不想这么做)都会有帮助,但添加X ms延迟将增加nX ms延迟
#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;
};
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(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;
}
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);
}
});
#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"