我有一个应用程序,它可能具有长期运行的任务,也可能有数千或数百万或结果。
这个特定的应用程序(下面的代码)没有任何价值,但它旨在提供一个需要在“数千个”结果中维护响应式 UI 的一般用例。
需要明确的是,我知道应该减少轮询 UI 的次数。我的问题是关于可以应用于此(和其他类似)场景以保持响应式 UI 的设计原则。
我的第一个想法是使用QTimer并每隔 200 毫秒处理一次所有“结果”,可以在此处找到一个示例,但需要添加。
哪些方法可用,哪些方法更适合保持响应式 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(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 加载时不执行任何操作,而不是打印“Launching”。调用该方法时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 线程上运行)。由于我正在模拟大量的“ui 更新”,如 所示int entries = 50000000;
,每次处理结果时,QFutureWatcher<IntStream>::resultReadyAt
都会调用 。
虽然它运行了 +/- 2 秒,但 UI 不会响应分别链接到on_thread1PauseResume_clicked()
和的“暂停”或“停止”点击on_thread1Stop_clicked
。