How to communicate a progressText from a QtConcurrent::run function (or similar) to a QFutureWatcher?

StackOverflow https://stackoverflow.com/questions/23438044

Question

If I launch some function for asynchronous execution using QtConcurrent::run, and am monitoring the returned future using a QFutureWatcher, what if anything can I do in that asynchronously executing function to communicate some progress text back which will result in the QFutureWatcher firing its progressTextChanged signal?

ie what I want to do is something like:

void fn() {
  ???->setProgressText("Starting);
  ...
  ???->setProgressText("halfway");
  ...
  ???->setProgressText("done!");
}

QFutureWatcher watcher;
connect(&watcher, SIGNAL(progressTextChanged(const QString&)), &someGuiThing, SLOT(updateProgress(const QString&)));
connect(&watcher, SIGNAL(finished(), &someGuiThing, SLOT(doStuff()));
QFuture<void> future=QConcurrent::run(fn);
watcher.setFuture(future);

However, big problem, the QtConcurrent::run documentation clearly states

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.

So what's the simplest thing I can do which will get me something functionally equivalent to what the above is trying to do? Do I have to abandon QtConcurrent::run? QFuture? Both? (And go back to QThread and queued connections?)

Was it helpful?

Solution

QFuture returned by QtConcurrent functions like QtConcurrent::mappedReduced() have progress information provided by the progressValue(), progressMinimum(), progressMaximum(), and progressText() functions. Unlike QtConcurrent::run() which does not provide such a thing automatically.

QtConcurrent::run() does not provide progress information automatically like QtConcurrent::mappedReduced(). But you can have your own progress reporting mechanism using signals. I don't think there is any other way which is straightforward.

OTHER TIPS

You can still use QFutureWatcher with QProgressDialog like in my example:

void hole_mark::get_frames_with_progress(const QString& movie, const QString& output) {
    Ptr<cv::VideoCapture> source = makePtr<VideoCapture>(movie.toUtf8().constData());

    auto frames = (int)(source->get(CAP_PROP_FRAME_COUNT));

    QProgressDialog dialog(tr("Importing frames: %1...").arg(frames), tr("Cancel"), 0, frames, this);
    dialog.setWindowModality(Qt::WindowModal);

    QFutureWatcher<void> futureWatcherProgress;
    QFutureInterface<void> promise;
    QFuture<void> future = promise.future();
    promise.reportStarted();

    QObject::connect(&futureWatcherProgress, SIGNAL(finished()), &dialog, SLOT(reset()));
    QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcherProgress, SLOT(cancel()));
    QObject::connect(&futureWatcherProgress, SIGNAL(progressValueChanged(int)), &dialog, SLOT(setValue(int)));
    QObject::connect(&futureWatcherProgress, SIGNAL(progressRangeChanged(int, int)), &dialog, SLOT(setRange(int, int)));
    QObject::connect(&futureWatcherProgress, SIGNAL(progressTextChanged(const QString&)), &dialog, SLOT(setLabelText(const QString&)));

    futureWatcherProgress.setFuture(future);
    promise.setThreadPool(QThreadPool::globalInstance());

    auto runnable = QRunnable::create([&, frames, source]() {

        promise.setProgressRange(0, frames);
        promise.setProgressValue(0);
        cv::Mat m;

        int frame = 0;

        while (!future.isCanceled()) {
            *source >> m;

            if (m.empty()) {
                break;
            }

            promise.setProgressValueAndText(++frame, tr("Importing %1 frame from: %2...").arg(frame).arg(frames));

            qDebug() << "frame: " << frame;
        }


        promise.reportFinished();
    });

    promise.setRunnable(runnable);

    QThreadPool::globalInstance()->start(runnable);

    // Display the dialog and start the event loop.
    dialog.exec();

    futureWatcherProgress.waitForFinished();

    // Query the future to check if was canceled.
    qDebug() << "Canceled?" << futureWatcherProgress.future().isCanceled();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top