Russian Qt Forum

Qt => Кладовая готовых решений => Тема начата: Авварон от Сентябрь 14, 2012, 14:26



Название: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Сентябрь 14, 2012, 14:26
Встала задача выполнения долгой операции в треде. Казалось бы - берем QtConcurrent и пользуемся. Но не тут-то было - в документации к QtConcurrent::run написано:
Цитировать
Note that the QFuture returned by QtConcurrent::run() does not support canceling, pausing, or progress reporting. The QFuture returned can only be used to query for the running/finished status and the return value of the function.
Погуглив, я ничего не нашел и полез в сорцы креатора. Оказывается, там есть чудный файлик runextensions.h (который можно, при желании, легко написать самому), который добавляет возможность уведомлять о прогрессе выполнения функции.

Вот пример использования (проект и runextensions.h можно скачать в аттаче):
Функция запускает задачу в потоке, а та пишет прогресс в диалог. Также можно отменить задачу кнопкой на диалоге.
Код:
#include <QApplication>

#include <QFuture>
#include <QFutureWatcher>
#include <QProgressDialog>

#include "runextensions.h"

//heavy work :)
static void doWork(QFutureInterface<void> &future)
{
    future.setProgressRange(0, 100);

    static const int iterations = 10*1000*1000;
    for (int i = 0; i < iterations; i++) {
        if (future.isCanceled())
            break;

        future.setProgressValue(100.0*i/iterations);

        QObject *object = new QObject;
        object->setObjectName("Object");
        delete object;
    }
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    app.setQuitOnLastWindowClosed(false);

    QProgressDialog progress;
    progress.setWindowTitle(QObject::tr("Work"));
    progress.setLabelText(QObject::tr("Working..."));
    progress.setRange(0, 100);

    QFutureWatcher<void> watcher;
    QObject::connect(&watcher, SIGNAL(finished()), &app, SLOT(quit()));
    QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &progress, SLOT(setValue(int)));
    QObject::connect(&progress, SIGNAL(canceled()), &watcher, SLOT(cancel()));

    QFuture<void> future = QtConcurrent::run(&doWork);
    watcher.setFuture(future);
    progress.show();

    return app.exec();
}


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Май 19, 2015, 21:26
Спасибо за находку! В документации об этом ни гу-гу.

Цитировать
Погуглив, я ничего не нашел и полез в сорцы креатора. Оказывается, там есть чудный файлик runextensions.h (который можно, при желании, легко написать самому), который добавляет возможность уведомлять о прогрессе выполнения функции.

А почему нельзя, например, передать объект QFuture напрямую в doWork()?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Май 22, 2015, 13:35
Потому что футура предназначена для чтения, ее нельзя (нет API) менять. Для этого сделан FurureInterface (aka std::promise)


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Май 22, 2015, 18:04
Понятно, спасибо. Правда я вызываю метод класса и у меня выдаёт ошибку при компиляции:
Цитировать
c:\qt\4.8.6\src\corelib\concurrent\qtconcurrentrun.h:115: error: C2064: term does not evaluate to a function taking 1 arguments

Пытаюсь по Вашей аналогии вызвать:
Код:
    QFuture<void> backgroundJob = QtConcurrent::run(report, &XMLReport::Export);

Определение этого метода в классе:
Код:
void XMLReport::Export(QFutureInterface<void> &future)

Как такой метод можно вызвать? А если он ещё и виртуальный (virtual) - вызовется ли правильный метод в наследуемом классе?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Май 24, 2015, 00:18
Попробовал обёртки по типу как написано в Qt - всё равно эта ошибка вылетает при компиляции, видимо это фишка только 5-й Qt, а в qt 4.8.6 не работает. Или нет?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Май 30, 2015, 09:44
Потому что у вас мембер ф-ия, надо this  передать.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Май 30, 2015, 12:44
Я передавал this, сделал обёртку над мембер функцией, всё равно ошибку выбивает, потому, что мне QFutureInterface туда передать нужно. Пришлось сигналы привязать напрямую в контейнере классов, а хотелось бы конечно спрятать внутри класса XMLReport и диалога с ProgressBar, передав им только QFutureInterface&.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 02, 2015, 19:08
Вы runextensions.h вообще скачали?)


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 02, 2015, 23:34
Конечно! :) Первым делом. И подключил.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: __Heaven__ от Июнь 03, 2015, 11:11
Да, забавненько, что про QFutureInterface нет статейки в доках. Я же для отображения прогресса использовал общие переменные. Из прогрессдиалога считывал каждые 100мс.
А в qthread как-то можно следить за прогрессом иначе? Не понятен смысл строчки
Цитировать
Note that the QFuture returned by QtConcurrent::run() does not support canceling, pausing, or progress reporting.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 03, 2015, 16:35
Да, забавненько, что про QFutureInterface нет статейки в доках. Я же для отображения прогресса использовал общие переменные. Из прогрессдиалога считывал каждые 100мс.
А в qthread как-то можно следить за прогрессом иначе? Не понятен смысл строчки
Цитировать
Note that the QFuture returned by QtConcurrent::run() does not support canceling, pausing, or progress reporting.

Как раз понятен - т.к. интерфейс в ф-ии не передается, то она и не поддерживает.

AlekseyK
Это странно, т.к. у вас он пытается юзать оверлоады из qtcore, судя по выхлопу компилятора.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 16:41
AlekseyK
Это странно, т.к. у вас он пытается юзать оверлоады из qtcore, судя по выхлопу компилятора.

Видимо так, не знаю как его нужно было заставить использовать runextensions, потратил много времени и так и сяк - не собралось, поэтому пока сигналы привязал напрямую. А хотелось бы конечно их спрятать внутри классов и передавать только QFutureInterface.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 03, 2015, 16:45
Пример у вас работает? Пробовали добавить туда мембер ф-ию?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 16:59
Пример у вас работает? Пробовали добавить туда мембер ф-ию?

Пример работает, если переделать на мембер функцию - не собирается:

Код
C++ (Qt)
#include <QApplication>
 
#include <QFuture>
#include <QFutureWatcher>
#include <QProgressDialog>
 
#include "runextensions.h"
 
class Work
{
 //heavy work :)
public:
 void doWork(QFutureInterface<void> &future)
 {
   future.setProgressRange(0, 100);
 
   static const int iterations = 10*1000*1000;
   for (int i = 0; i < iterations; i++) {
     if (future.isCanceled())
       break;
 
     future.setProgressValue(100.0*i/iterations);
 
     QObject *object = new QObject;
     object->setObjectName("Object");
     delete object;
   }
 }
};
 
int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   app.setQuitOnLastWindowClosed(false);
 
   QProgressDialog progress;
   progress.setWindowTitle(QObject::tr("Work"));
   progress.setLabelText(QObject::tr("Working..."));
   progress.setRange(0, 100);
 
   QFutureWatcher<void> watcher;
   QObject::connect(&watcher, SIGNAL(finished()), &app, SLOT(quit()));
   QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &progress, SLOT(setValue(int)));
   QObject::connect(&progress, SIGNAL(canceled()), &watcher, SLOT(cancel()));
 
   Work work;
 
   QFuture<void> future = QtConcurrent::run(work, &Work::doWork);
   watcher.setFuture(future);
   progress.show();
 
   return app.exec();
}
 


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 03, 2015, 17:03
Ну сигнатуру ф-ии всё-таки смотреть надо:)
Код:
QtConcurrent::run(&Work::doWork, &work);


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 17:07
ДААААА???? :)))))))))))))) Сейчас попробуем ещё тогда. :) Спасибо! Вот на это-то времени и не хватило разобраться.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 03, 2015, 17:15
Ну как бэ
Код:
template <typename Class, typename T>
QFuture<T> run(void (Class::*fn)(QFutureInterface<T> &), Class *object);


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 17:24
Ну да, но на примерах оно лучше :)


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 18:27
Собралось, спасибо! Попробуем переключить сигналы.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 03, 2015, 18:50
Ура! Всё заработало, можно отмечать тему как решённую!


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 12, 2015, 19:34
Правда ещё одна проблема всплыла, не всё так красиво получилось как хотелось: сигналы из процесса не все доходят до диалога, останавливаются где-то на 60-95% почему-то: значение и текст. Пришлось опять привязать сигналы напрямую: Процесс -> ProgressDialog. Что за ерунда, почему так происходит? Если привязываем процесс -> вызов QFutureInterface::setProgressValueAndText(progressValue, message) -> ProgressDialog, то  сигналы теряются в конце.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 12, 2015, 20:38
Хз, не замечал. Установка значения происходит? как обрабатываете finished? репортите ли завершение?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 12, 2015, 21:45
Хз, не замечал. Установка значения происходит?
Конечно, процесс заканчивается так:
Код
C++ (Qt)
futureInterface.setProgressValueAndText(100, "C-CDA document export finished!");

как обрабатываете finished?
Код
C++ (Qt)
void BackgroundWorkDialog::setFuture(const QFuture<void> &future)
{
 myProcessWatcher.setFuture(future);
 
 // Connect signals
 connect(&myProcessWatcher, SIGNAL(progressValueChanged(int)),
         progressBar, SLOT(setValue(int)));
 connect(&myProcessWatcher, SIGNAL(progressTextChanged(QString)),
         label, SLOT(setText(QString)));
 connect(&myProcessWatcher, SIGNAL(finished()), this, SLOT(proccessFinish()));
}
 
void BackgroundWorkDialog::proccessFinish()
{
 // disconnect dialog hiding
 disconnect(button, SIGNAL(clicked()), this, SLOT(hide()));
 
 if(isHidden())
   show();
 
 button->setText("Done!");
 connect(button, SIGNAL(clicked()), this, SLOT(accept()));
 connect(this, SIGNAL(accepted()), this, SLOT(deleteLater()));
 connect(this, SIGNAL(rejected()), this, SLOT(deleteLater()));
 
 emit finished();
}

репортите ли завершение?
Вызывается точно, т.к. кнопка ВСЕГДА меняет текст на "Done!", значит future или futureWatcher вызывает finished().


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 12, 2015, 22:56
Футуру лучше ставить после коннектов. Можете схематичный код таски привести?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 12, 2015, 23:09
Футуру лучше ставить после коннектов.

Можете схематичный код таски привести?
Конечно. XML экспорт, где после каждой секции выводится:
Код
C++ (Qt)
 
 reportProgress(10, "Generating ... section...");
 ...
 reportProgress(100, "Document export finished!");

Код
C++ (Qt)
void XMLReport::reportProgress(int progressValue, const QString &message)
{
 future().setProgressValueAndText(progressValue, message);
 
 emit progressValueChanged(progressValue);
 if(!message.isEmpty())
   emit progressTextChanged(message);
}
 

future() возвращает QFutureInterface, который передаётся при запуске процесса.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Igors от Июнь 13, 2015, 06:27
Боже, как это все запутано, навязывается какая-то футура, с которой надо мучительно разбираться... Не лучше ли по-простому: завести атомарный счетчик и делать ему ++ в каждом расчете. А главная нитка следит за ним по таймеру и обновляет индикатор. Так по крайней мере нет расходов на сигналы (а то часто работает на индикатор а не на расчет).


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 13, 2015, 10:07
Не лучше ли по-простому: на ассемблере?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Old от Июнь 13, 2015, 10:54
Боже, как это все запутано, навязывается какая-то футура, с которой надо мучительно разбираться... Не лучше ли по-простому: завести атомарный счетчик и делать ему ++ в каждом расчете. А главная нитка следит за ним по таймеру и обновляет индикатор. Так по крайней мере нет расходов на сигналы (а то часто работает на индикатор а не на расчет).
К сожалению, задач может быть больше одной, соответственно и индикаторов понадобиться больше одного, значит и атомарный счетчик понадобиться не один.
В общем, получится еще запутанней, чем здесь. :)
Хотя я ничего запутанного в future не наблюдаю.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Igors от Июнь 13, 2015, 11:47
Кстати о птичках: а что произойдет если задача выбросит исключение?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 13, 2015, 15:18
Кстати о птичках: а что произойдет если задача выбросит исключение?

Вы так кичитесь тем, что не читаете букварь, что регулярно ставите себя в дурацкое положение. Не позорьтесь, почитайте. Гугл std::future, std::promise.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: AlekseyK от Июнь 13, 2015, 19:57
Это всё интересно и познавательно, но всё же почему не все сигналы от future доходят?


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Old от Июнь 13, 2015, 20:00
Это всё интересно и познавательно, но всё же почему не все сигналы от future доходят?
Вы бы выложили компилябельный пример, как вы это делаете. Был бы проще.


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Igors от Июнь 14, 2015, 10:10
Кстати о птичках: а что произойдет если задача выбросит исключение?

Вы так кичитесь тем, что не читаете букварь, что регулярно ставите себя в дурацкое положение.
???  Да я здесь даже слова "букварь" не говорил :) И как это связано с вопросом про исключение?

Не позорьтесь, почитайте. Гугл std::future, std::promise.
А зачем мне их читать если я использую OpenMP - что на мой взгляд гораздо мощнее и солиднее. Заметьте, я не говорю Вам "не позорьтесь, читайте OpenMP"  :)

std - стандартные решения популярных задач. Ну вот не было футуры, надо было городить флажок, как-то организовать ожидание, вот сделали что-то штатное, ну молодцы, спасибо. Но откуда глупейшее предубеждение что этим штатным обязательно, непременно нужно пользоваться, иначе - несмываемый позор? :) Даже "чисто объективно" никто не обещал что оно окажется здесь лучшим. Напр та же посылка сигналов может заметно тормозить если единица задачи мала.

Вы определенно "заучились" - поверьте, не тот программист лучше кто "больше знает"  :)


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Old от Июнь 14, 2015, 10:37
А зачем мне их читать если я использую OpenMP
А зачем тогда вопросы задаете, если оно вам не надо? :)

что на мой взгляд гораздо мощнее и солиднее.
Детский сад. :)


Название: Re: QtConcurrent::run с прогрессом выполнения
Отправлено: Авварон от Июнь 14, 2015, 11:20

???  Да я здесь даже слова "букварь" не говорил :) И как это связано с вопросом про исключение?
В букваре написано что происходит при выбросе исключения.


А зачем мне их читать если я использую OpenMP - что на мой взгляд гораздо мощнее и солиднее. Заметьте, я не говорю Вам "не позорьтесь, читайте OpenMP"  :)
Чтобы уметь правильно пользоваться инструментом.

Вы определенно "заучились" - поверьте, не тот программист лучше кто "больше знает"  :)
Да-да, не тот, кто больше знает, а кто городит свои велосипеды потому что "букварь для лохов".