结合QtConcurrent调用

时间:2018-04-12 16:55:15

标签: c++ qt promise qt5

我有三个方法,并且所有方法都返回一个字符串,我想使用QtConcurrent运行所有这些方法并将它们返回到单个列表或类似的内容中。 QtConcurrent::mapped是理想的,因为它返回一个迭代器,但我只能运行一个方法。 在JavaScript中有promise.all([method_a, method_b, method_c]),它会自动将它们的返回合并到一个结果(迭代器)中。 如何在Qt中做到这一点?

4 个答案:

答案 0 :(得分:2)

由于没有内置方法可以做到这一点,你可以自己编写一个类来保持期货在一起并在完成所有任务时返回一组结果。这里唯一的限制是由于c ++的强类型性质:QtConcurrent::run返回的每个未来都包含被调用的函数结果,其类型在编译时给出,作为QFuture模板参数。如果被调用函数的返回类型彼此不同怎么办?在我提供的示例中,它们都返回相同的类型,但我认为可以使用QVariant来表示该含义并远离它。

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

Promise类是一个类模板,其中一个模板参数与QFuture的模板参数匹配。观察到的期货在构造函数中传递,而then方法接受完成回调作为其唯一参数。

Watcher类提供了一个插槽,用于从插槽中捕获QFutureWatcher::finished' signals. Each instance knows the promise object through a pointer to its PromiseInterface and will call已完成`,传入已完成的未来的id。

当所有期货结束时,调用回调函数并传入结果向量。

在一个非常简单的用法示例中,我们可以同时执行此函数:

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

并将此回调传递给承诺then

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

我们的主要内容:

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

大约一秒后,这是预期的输出:

QVector(1, 2, 3)

以防万一我想知道为什么我放入三个课程,而不是直接Promise扩展QObject,并实现finished插槽本身:Qt不让我这样做。将Q_OBJECT宏添加到类模板时,显式编译器错误将提示: Q_OBJECT 不支持的模板类。

答案 1 :(得分:2)

由于您有多种方法可以调用,因此您可以将它们作为一系列仿函数传递给QtConcurrent::mapped的第一个参数。映射函子是一个apply仿函数,它接受一个表示方法调用的函子并返回调用它的结果。

首先,让我们上课:

// 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"); }
};

apply_t仿函数调用作为参数传递给它的方法:

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

让我们可以方便地从一系列仿函数的类型中制作这样的涂抹器:

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

为方便起见,我们还有一个矢量发生器。 make_unique等等:

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

然后,问题变得相当简单。首先,我们创建一个将被调用的绑定方法的向量。然后我们将调用的方法以及操作它们的应用程序传递给QtConcurrent::mappedresults()按顺序列出了方法调用的所有结果。

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

我们可以使用lambda来提供apply_t期望的result_type成员类型,而不是创建自定义QtConcurrent::mapped类。有关包裹lambda的详细信息,请参阅this answerrest of this answer提供了此类包装的示例。

答案 2 :(得分:1)

您正在寻找的方法是QFuture::results()

  

<强> QList<T> QFuture::results() const

     

返回未来的所有结果。如果结果不是   立即可用,此功能将阻止并等待它们   变得可用。

从Qt自己的QtConcurrent::mapped example扩展:

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

QList<QImage> images = ...;
QList<QImage> thumbnails = QtConcurrent::mapped(images, scaled).results();

答案 3 :(得分:1)

我知道AsyncFuture是一个c ++库,它将调用转换为QFuture类型,并像Javascript中的Promise对象一样使用它(将不同类型的多个未来合并为一个未来的对象) 。不幸的是我从来没用过它!但有关此参考Qt博客Multithreaded Programming with Future & Promise

的详细信息