3

I have three methods and all of them returns a string, I want to run all of them using QtConcurrent and get their return into a single list or something like that. QtConcurrent::mapped is ideal as it returns an iterator but I can only run one method at time. In JavaScript there's promise.all([method_a, method_b, method_c]), it will automatically merge their return into a single result (iterator). How to do that in Qt?

p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
Mr Gisa
  • 53
  • 5
  • 1
    Use a QVector and append, if the return is a blocking call. If not `QtConcurrent` is wrong, use QThread and send the value back as a merged single value when all 3 values are available in the thread. There is no syntax to merge all returns in a single value what you would know from Perl or Javascript. – user3606329 Apr 13 '18 at 09:00
  • @user3606329 Of course there is a merge operation, and `QFuture` provides it already via the `results()` method! – Kuba hasn't forgotten Monica Apr 13 '18 at 18:00
  • @ Kuba Ober: yes I know about `QFuture::results()`. The OP wanted probably an one-liner to merge the results of 3 QtConcurrent calls and showed the promise.all method from Javascript as reference. – user3606329 Apr 13 '18 at 20:25

4 Answers4

2

Since there's no built in way to do that, you can work out a class on your own to keep futures together and return a collection of results when all tasks completed. The only limitation, here, is due to the strongly typed nature of c++: each future returned by QtConcurrent::run holds the called function result, the type of which is given at compile time, as the QFuture template parameter. What if the return types of the called functions differ from each other? In the example I provide, they all return the same type, but I think one could use QVariant for that mean and get away with it.

In a promise.h:

#ifndef PROMISE_H
#define PROMISE_H

#include <QtConcurrent/QtConcurrentRun>
#include <QFutureWatcher>

class PromiseInterface
{
public:
    virtual ~PromiseInterface() = default;
    virtual void finished(int id) = 0;
};

class Watcher : public QObject
{
    Q_OBJECT
    int _id;
    PromiseInterface * _promise;

public slots:
    void finished()
    {
       _promise->finished(_id);
       deleteLater();
    }

public:
    Watcher(int id, PromiseInterface * promise)
        : _id(id),
          _promise(promise)
    {}
};

template <typename T>
class Promise : public PromiseInterface
{
    friend class Watcher;
    void finished(int id) override
    {
        _resolved++;
        _results[id] = _watchers[id]->result();
        delete _watchers[id];

        if(_resolved == _results.size())
        {
            if(_callback != nullptr)
            {
                _callback(_results);
            }
        }
    }
    QList<QFutureWatcher<T> *> _watchers;
    QVector<T> _results;
    void (*_callback)(QVector<T>);
    int _resolved;
public:
    Promise(QList<QFuture<T>> futures)
    {
        _resolved = 0;
        _callback = nullptr;
        _results.resize(futures.size());

        int i=0;
        for(auto f : futures)
        {
            QFutureWatcher<T> * watcher = new QFutureWatcher<T>();
            watcher->setFuture(f);
            QObject::connect(watcher, &QFutureWatcher<T>::finished, new Watcher(i++, this), &Watcher::finished);
            _watchers.append(watcher);
        }
    }
    void then(void (*callback)(QVector<T>)) { _callback = callback; }
};

#endif // PROMISE_H

The Promise class is a class template with a single template parameter that matches the one of QFuture. The observed futures are passed in the constructor, while the then method accepts a the completion callback as its only argument.

The Watcher class provides a slot to catch QFutureWatcher::finished' signals. Each instance knows the promise object through a pointer to itsPromiseInterfaceand will callfinished` from the slot, passing in the id of the future that completed.

When all futures finished, the callback function is called with the vector of results passed in.

In a very simple usage example, we can execute this function concurrently:

#include <unistd.h>
int f(int r) { sleep(1); return r;}

and pass this callback to the promise then:

void callback(QVector<int> results)
{
    qDebug() << results;
}

Our main:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QList<QFuture<int>> futures = {
        QtConcurrent::run(&f, 1),
        QtConcurrent::run(&f, 2),
        QtConcurrent::run(&f, 3)
    };

    Promise<int> promise(futures);
    promise.then(callback);

    return a.exec();
}

After about a second, this is the expected output:

QVector(1, 2, 3)

Just in case one wonders why I put in three classes, instead of making Promise extend QObject directly, and implement the finished slot itself: Qt is not letting me do that. When the Q_OBJECT macro is added to a class template, an explicit compiler error will prompt: Template classes not supported by Q_OBJECT.

p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
2

Since you have several methods to call, you can pass them as a sequence of functors as the first argument to QtConcurrent::mapped. The mapping functor would be an apply functor that takes a functor representing the method call and returns the result of invoking it.

First, let's have our class:

// https://github.com/KubaO/stackoverflown/tree/master/questions/concurrent-combine-49802153
#include <QtConcurrent>
#include <functional>
#include <initializer_list>
#include <type_traits>

class Cls {
public:
   QString method1() const { return QStringLiteral("10"); }
   QString method2() const { return QStringLiteral("20"); }
   QString method3() const { return QStringLiteral("30"); }
};

The apply_t functor invokes the method passed to it as an argument:

template <class Method> struct apply_t {
   using result_type = typename std::result_of_t<Method()>;
   auto operator()(Method method) {
      return method();
   }
};

Let's make it convenient to make such applicators from the type of a sequence of functors to be called:

template <class Sequence, class A = apply_t<typename std::decay_t<Sequence>::value_type>>
A make_apply(Sequence &&) { return {}; }

For convenience, we'll also have a vector generator in the spirit of e.g. make_unique, etc.:

template <class T> QVector<T> make_vector(std::initializer_list<T> init) {
   return {init};
}

Then, the problem becomes fairly simple. First, we make a vector of bound methods that will be called. Then we pass the methods to call, as well as the applicator to operate on them, to QtConcurrent::mapped. The results() gives a list of all results of the method calls, in sequence.

int main() {
   Cls obj;
   auto const methods = make_vector({
                                 std::bind(&Cls::method1, &obj),
                                 std::bind(&Cls::method2, &obj),
                                 std::bind(&Cls::method3, &obj)
                              });
   QFuture<QString> result =
         QtConcurrent::mapped(methods, make_apply(methods));
   Q_ASSERT((result.results() == QStringList{"10", "20", "30"}));
}

Instead of making a custom apply_t class, we could use a lambda, wrapped to provide the result_type member type that QtConcurrent::mapped expects. See this answer for details of wrapping the lambda. The rest of this answer provides examples of such wrapping.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Is `result_type` for? _These function objects can be used to add state to a function call_.,how make of this sentence? – Crawl.W Jun 01 '20 at 09:02
  • @Crawl.W `result_type` is the type returned by `Method`. In order to be used in some circumstances, functors (callable objects) need to have a `result_type` type member, or an equivalent type in a trait class. – Kuba hasn't forgotten Monica Jun 02 '20 at 00:40
1

The method you're looking for is QFuture::results():

QList<T> QFuture::results() const

Returns all results from the future. If the results are not immediately available, this function will block and wait for them to become available.

Expanding from Qt's own QtConcurrent::mapped example:

QImage scaled(const QImage &image)
{
   return image.scaled(100, 100);
}

QList<QImage> images = ...;
QList<QImage> thumbnails = QtConcurrent::mapped(images, scaled).results();
Lukas W
  • 305
  • 1
  • 9
  • You didn't understand what I meant, I want to call three methods at the same time, imagine that there's `scaled1`, `scaled2`, `scaled3` and not only `scaled`. and get the result of all of them like the `promise.all` from javascript. – Mr Gisa Apr 12 '18 at 17:29
  • @MrGisa get them one by one and concatenate. – Dmitry Sazonov Apr 13 '18 at 09:59
1

I know AsyncFuture is a c++ library that converts calls into a QFuture type and uses it like a Promise object in Javascript (Combine multiple futures with different type into a single future object). unfortunately i never used it! but there are details on this reference Qt Blog Multithreaded Programming with Future & Promise

Mohammad Kanan
  • 4,452
  • 10
  • 23
  • 47