diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 4a66d7f674b..58235cd814f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -3,7 +3,10 @@ #include "tasktree.h" +#include +#include #include +#include namespace Tasking { @@ -1444,6 +1447,54 @@ bool TaskTree::isRunning() const return d->m_root && d->m_root->isRunning(); } +bool TaskTree::runBlocking(const QFuture &future, int timeoutMs) +{ + if (isRunning() || future.isCanceled()) + return false; + + bool ok = false; + QEventLoop loop; + + const auto finalize = [&loop, &ok](bool success) { + ok = success; + // Otherwise, the tasks from inside the running tree that were deleteLater() + // will be leaked. Refer to the QObject::deleteLater() docs. + QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection); + }; + + QFutureWatcher watcher; + connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::stop); + watcher.setFuture(future); + + connect(this, &TaskTree::done, &loop, [finalize] { finalize(true); }); + connect(this, &TaskTree::errorOccurred, &loop, [finalize] { finalize(false); }); + start(); + if (!isRunning()) + return ok; + + QTimer timer; + if (timeoutMs) { + timer.setSingleShot(true); + timer.setInterval(timeoutMs); + connect(&timer, &QTimer::timeout, this, &TaskTree::stop); + timer.start(); + } + + loop.exec(QEventLoop::ExcludeUserInputEvents); + if (!ok) { + auto nonConstFuture = future; + nonConstFuture.cancel(); + } + return ok; +} + +bool TaskTree::runBlocking(int timeoutMs) +{ + QPromise dummy; + dummy.start(); + return runBlocking(dummy.future(), timeoutMs); +} + int TaskTree::taskCount() const { return d->m_root ? d->m_root->taskCount() : 0; diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index bbee1a3fe8c..462eb5bd2eb 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -9,6 +9,11 @@ #include #include +QT_BEGIN_NAMESPACE +template +class QFuture; +QT_END_NAMESPACE + namespace Tasking { class ExecutionContextActivator; @@ -369,6 +374,12 @@ public: void stop(); bool isRunning() const; + // Helper methods. They execute a local event loop with ExcludeUserInputEvents. + // The passed future is used for listening to the cancel event. + // Don't use it in main thread. To be used in non-main threads or in auto tests. + bool runBlocking(const QFuture &future, int timeoutMs = 0); + bool runBlocking(int timeoutMs = 0); + int taskCount() const; int progressMaximum() const { return taskCount(); } int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded