QtConcurrent: Early stop a MappedReduced/FilteredReduced from inside
-
I am developping a "contains" like function, using the QtConcurrent MappedReduced function, to compute values from data stored in a list, then compare them to the value to search for.
To save time, I would like to stop the process, when I found the first match.
Is there a way to do that?
I tried to capture the QFuture, to cancel the process from the Reduce functor (that is call by only one thread at a time), but that crashes:
float compute(const QString &); bool compare(const float &, const float &); bool contains(const float &search, const QStringList &list) { QFuture<bool> process; std::function<float(const QString &)> map = [](const QString &data) { return compute(data); }; std::function<void(bool&, const float &)> reduce = [&search, &process](bool &found, const float &value) { if( compare(search, value) == true ) { if( found == false ) process.cancel(); found = true; //throw found; } }; process = QtConcurrent::mappedReduced<bool>(list, map, reduce, false); process.waitForFinished(); return process.result(); }
Alternatively, I can share the result with the mapped functor, to skip the following computations, once a result has been found.
-
I found!
This was not a "multi threads" issue. The crash was from the return statement. This code works:float compute(const QString &); bool compare(const float &, const float &); bool contains(const float &search, const QStringList &list) { QFuture<bool> process; int count = 0; bool found = false; std::function<float(const QString &)> map = [](const QString &data) { return compute(data); }; std::function<void(bool&, const float &)> reduce = [&found, &count, &search, &process](bool &, const float &value) { if( compare(search, value) == true ) { if( found == false ) process.cancel(); found = true; } ++count; }; process = QtConcurrent::mappedReduced<bool>(list, map, reduce); process.waitForFinished(); qDebug() << count; return found; }
(probably safer with a QFutureWatcher)
But I also read this old thread, and yeye_olive oriented me to this other solution:
float compute(const QString &); bool compare(const float &, const float &); bool contains(const float &search, const QStringList &list) { int count = 0; bool found = false; QFutureWatcher<bool> watcher; QEventLoop loop; QObject::connect(&watcher, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit); std::function<float(const QString &)> map = [](const QString &data) { return compute(data); }; std::function<void(bool&, const float &)> reduce = [&found, &count, &search, &loop](bool &, const float &value) { if( compare(search, value) == true ) { if( found == false ) QMetaObject::invokeMethod(&loop, "quit", Qt::QueuedConnection); found = true; } ++count; }; watcher.setFuture( QtConcurrent::mappedReduced<bool>(list, map, reduce) ); loop.exec(); watcher.cancel(); watcher.waitForFinished(); qDebug() << count; return found; }
And finally, I implemented this solution, which save the reduce functor (the half of the threads):
class Result : public QObject { public: Result(QObject *parent=nullptr) : QObject(parent), mFound(false){} signals: void stop(); public slots: void found(void) { mFound = true; emit stop(); } public: bool mFound; Q_OBJECT }; float compute(const QString &); bool compare(const float &, const float &); bool contains(const float &search, const QStringList &list) { int count = 0; Result result; QFutureWatcher<bool> watcher; QEventLoop loop; QObject::connect(&watcher, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit); QObject::connect(&result, &Result::stop, &loop, &QEventLoop::quit); std::function<float(const QString &)> map = [&result, &count, &search](const QString &data) { ++count; if( compare(search, compute(data)) == true ) QMetaObject::invokeMethod(&result, "found", Qt::QueuedConnection); return false; // need to return a value }; watcher.setFuture( QtConcurrent::mapped(list, map) ); loop.exec(); watcher.cancel(); watcher.waitForFinished(); qDebug() << count; return result.mFound; }
Are these solutions totally "thread-safe"? I don't really have experience in "multi-threading" coding.
-
Hi,
Out of curiosity, why implement it that way ? You are in fact blocking your application because you are waiting on the result.
-
No, it's not about a performance issue.
I was just not satisfied with leaving this function running until the end, since I already had my result and the rest will not bring me anything more.
If I need to write it in a simple 'foreach' loop, I would write something likefor( value : list ) if( value == search ) return true; return false;
And I wanted to keep the same spirit with 'mappedReduced' (as long as this doesn't violate any "thread-rules" and without having to "hack" QtConcurrent).
But yes, in fact I'm blocking my application untile I receive the results. My other methods that need to perform an action on all the items use 'blockingMapped', 'blockingMappedReduced' and 'blockingFiltered'. It's for a library that I am writing. And the applications that will use it are procedurale, not a GUI (or, maybe later...who knows?). But they may have to handle very long lists, and the 'compute' function are heavy. So QtConcurrent is a welcome help.
I think I'll keep the first solution, the most elegant one in my eyes, if I don't get any warning.