7

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?)

Nejat
  • 31,784
  • 12
  • 106
  • 138
timday
  • 24,582
  • 12
  • 83
  • 135

2 Answers2

4

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.

Nejat
  • 31,784
  • 12
  • 106
  • 138
  • OK, but then what do I actually call in `fn()` to get the progress information to update? Obviously it'll need to have something passed into it... but what? There seems to be a QFutureInterfaceBase which has progress info setting methods, but QFuture doesn't actually inherit it. Hmmm... obviously the next step is to look at the `QtConcurrent::mappedReduced()` implementation and see how it does it... – timday May 03 '14 at 08:52
  • 1
    I think using signals to report progress is the most convenient way when using QtConcurrent::run(). – Nejat May 03 '14 at 10:12
  • 2
    Yes that's what I ended up doing: sending status messages out of the concurrent function using QMetaObject::invokeMethod with QueuedConnection to a slot monitoring its progress (actually, BlockingQueuedConnection seemed to result in smoother display... I send a lot of messages). Bit more on the context at http://qt-project.org/forums/viewthread/41012/#174738 – timday May 03 '14 at 19:02
0

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();
}