我有三个方法,它们都返回一个字符串,我希望使用QtConcurrent运行所有方法,并将它们返回到一个列表或类似的列表中。QtConcurrent::mapped是理想的,因为它返回一个迭代器,但我只能同时运行一个方法。在JavaScript中有promise.all([method_a, method_b, method_c]),它将自动将它们的返回合并到一个结果(迭代器)中。如何在Qt中做到这一点?
发布于 2018-04-13 18:02:24
因为要调用几个方法,所以可以将它们作为函子序列传递给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::mapped。results()按顺序给出了方法调用的所有结果的列表。
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来提供result_type成员类型,而不是定制的result_type类,这是QtConcurrent::mapped所期望的。有关包装lambda的详细信息,请参阅这个答案。其余的答案提供了此类包装的示例。
发布于 2018-04-13 09:56:51
由于没有内置的方法来这样做,所以您可以自己设计一个类来保持未来,并在所有任务完成后返回一组结果。这里唯一的限制是c++的强类型性质:QtConcurrent::run返回的每个未来都保存被调用的函数结果,其类型在编译时给出,作为QFuture模板参数。如果被调用函数的返回类型彼此不同怎么办?在我所提供的示例中,它们都返回相同的类型,但我认为可以使用QVariant来实现这个目的,并将其置之不理。
在一份承诺中
#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_HPromise类是一个类模板,具有一个与QFuture的参数匹配的模板参数。观察到的期货在构造函数中传递,而then方法接受一个完成回调作为它的唯一参数。
QFutureWatcher::finished' signals. Each instance knows the promise object through a pointer to itsPromiseInterfaceand will callfinished`类提供了一个从插槽捕获Watcher的槽,传递的是将来完成的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不支持的模板类。
发布于 2018-04-12 17:05:52
你要找的方法是QFuture::results()
QList<T> QFuture::results() const返回未来的所有结果。如果结果不能立即可用,则此函数将阻止并等待它们可用。
从Qt自己的示例扩展
QImage scaled(const QImage &image)
{
return image.scaled(100, 100);
}
QList<QImage> images = ...;
QList<QImage> thumbnails = QtConcurrent::mapped(images, scaled).results();https://stackoverflow.com/questions/49802153
复制相似问题