У меня есть приложение с потенциально длительными задачами, а также, возможно, с тысячами или миллионами результатов.
Это конкретное приложение (код ниже) не имеет никакой ценности, но оно предназначено для обеспечения общего варианта использования необходимости поддерживать отзывчивый пользовательский интерфейс среди «тысяч» результатов.
Чтобы было ясно, я знаю, что нужно уменьшить количество опросов пользовательского интерфейса. Мой вопрос касается принципов проектирования, которые можно применить к этому (и другим подобным) сценариям, чтобы сохранить адаптивный пользовательский интерфейс.
Моя первая мысль - использовать QTimer и обрабатывать все "результаты" каждый, например. 200 мс, пример можно найти здесь, но нуждается в доработке.
Какие методы доступны и какие предпочтительны для сохранения адаптивного пользовательского интерфейса?
Простой пример, который я пытаюсь объяснить, заключается в следующем. У меня есть пользовательский интерфейс, который:
генерирует список целых чисел,
передает его в сопоставленную функцию для pow(x,2) значения и
измерять прогресс
При запуске этого приложения нажмите кнопку «Пуск», чтобы запустить приложение, но из-за частоты обработки результатов QueuedConnection: QFutureWatcher ::resultReadyAt, пользовательский интерфейс не может реагировать ни на какие клики пользователя, поэтому попытки «приостановить» или «остановить» (отменить) бесполезны.
Оболочка для функции 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;
}
Объяснение того, почему пользовательский интерфейс «не отвечает».
Пользовательский интерфейс загружается без выполнения каких-либо действий, кроме печати «Запуск». Когда метод 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);
}
});
Это подключение прослушивает результат из будущего и действует в соответствии с ним (эта функция подключения выполняется в потоке пользовательского интерфейса). Поскольку я эмулирую огромное количество «обновлений пользовательского интерфейса», показанных int entries = 50000000;
, каждый раз, когда обрабатывается результат, вызывается QFutureWatcher<IntStream>::resultReadyAt
.
Пока это выполняется в течение +/- 2 с, пользовательский интерфейс не реагирует на клики «пауза» или «стоп», связанные с on_thread1PauseResume_clicked()
и on_thread1Stop_clicked
соответственно.