From 2f15927f01ceef0aca490746302a5ea57ea9441c Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Wed, 20 May 2020 11:39:39 +0200 Subject: Add a way of notifying QFutureWatcher when pause is in effect Because setting QFutureInterface to paused state does not mean that the computations that are already in progress will stop immediately, it may be useful to get notified when pause actually takes effect. Introduced the QFutureWatcher::suspended() signal, to be emitted when there are no more computations in progress, and no more result ready or progress reporting signals will be emitted, i.e. when pause took effect. Added {QFuture, QFutureWatcher}::isSuspended() methods for checking if pause took effect. QtConcurrent will now to send QFutureCallOutEvent::Suspended event when the state is paused and there are no more active threads. [ChangeLog][QtCore][QFutureWatcher] Added a new QFutureWatcher::suspended() signal, to be emitted when pause took effect, meaning that there are no more computations in progress. Added {QFuture, QFutureWatcher}::isSuspended() methods for checking if pause took effect. Fixes: QTBUG-12152 Change-Id: I88f2ad24d800cd6293dec63977d45bd35f9a09f0 Reviewed-by: Jarek Kobus --- tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 38 +++++++++ .../thread/qfuturewatcher/tst_qfuturewatcher.cpp | 91 ++++++++++++++++++++++ 2 files changed, 129 insertions(+) (limited to 'tests') diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index a7f308de59..d647ce6eba 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -113,6 +113,7 @@ private slots: void iterators(); void iteratorsThread(); void pause(); + void suspend(); void throttling(); void voidConversions(); #ifndef QT_NO_EXCEPTIONS @@ -1334,6 +1335,43 @@ void tst_QFuture::pause() Interface.reportFinished(); } +void tst_QFuture::suspend() +{ + QFutureInterface interface; + + interface.reportStarted(); + QFuture f = interface.future(); + QVERIFY(!interface.isSuspended()); + + interface.reportSuspended(); + QVERIFY(!interface.isSuspended()); + + // pause + interface.togglePaused(); + QVERIFY(!interface.isSuspended()); + QVERIFY(interface.isPaused()); + + interface.reportSuspended(); + QVERIFY(interface.isSuspended()); + QVERIFY(interface.isPaused()); + + // resume + interface.togglePaused(); + QVERIFY(!interface.isSuspended()); + QVERIFY(!interface.isPaused()); + + // pause again + interface.togglePaused(); + interface.reportSuspended(); + + interface.reportCanceled(); + QVERIFY(!interface.isSuspended()); + QVERIFY(!interface.isPaused()); + QVERIFY(interface.isCanceled()); + + interface.reportFinished(); +} + class ResultObject : public QObject { Q_OBJECT diff --git a/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp b/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp index 8b332e67fe..13fb587607 100644 --- a/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp +++ b/tests/auto/corelib/thread/qfuturewatcher/tst_qfuturewatcher.cpp @@ -58,6 +58,8 @@ private slots: void changeFuture(); void cancelEvents(); void pauseEvents(); + void suspended(); + void suspendedEvents(); void finishedState(); void throttling(); void incrementalMapResults(); @@ -723,6 +725,95 @@ void tst_QFutureWatcher::pauseEvents() } } +void tst_QFutureWatcher::suspended() +{ + QFutureWatcher watcher; + QSignalSpy resultReadySpy(&watcher, &QFutureWatcher::resultReadyAt); + QSignalSpy pausedSpy(&watcher, &QFutureWatcher::paused); + QSignalSpy suspendedSpy(&watcher, &QFutureWatcher::suspended); + QSignalSpy finishedSpy(&watcher, &QFutureWatcher::finished); + + const int numValues = 25; + std::vector values(numValues, 0); + std::atomic_int count = 0; + + QThreadPool pool; + pool.setMaxThreadCount(3); + + QFuture future = QtConcurrent::mapped(&pool, values, [&](int value) { + ++count; + // Sleep, to make sure not all threads will start at once. + QThread::msleep(50); + return value; + }); + watcher.setFuture(future); + + // Allow some threads to start before pausing. + QThread::msleep(200); + + watcher.pause(); + watcher.pause(); + QTRY_COMPARE(suspendedSpy.count(), 1); // suspended() should be emitted only once + QCOMPARE(pausedSpy.count(), 2); // paused() is emitted as many times as requested + + // Make sure QFutureWatcher::resultReadyAt() is emitted only for already started threads. + const auto resultReadyAfterPaused = resultReadySpy.count(); + QCOMPARE(resultReadyAfterPaused, count); + + // Make sure no more results are reported before resuming. + QThread::msleep(200); + QCOMPARE(resultReadyAfterPaused, resultReadySpy.count()); + resultReadySpy.clear(); + + watcher.resume(); + QTRY_COMPARE(finishedSpy.count(), 1); + + // Make sure that no more suspended() signals have been emitted. + QCOMPARE(suspendedSpy.count(), 1); + + // Make sure the rest of results were reported after resume. + QCOMPARE(resultReadySpy.count(), numValues - resultReadyAfterPaused); +} + +void tst_QFutureWatcher::suspendedEvents() +{ + QFutureInterface iface; + iface.reportStarted(); + + QFutureWatcher watcher; + + QSignalSpy pausedSpy(&watcher, &QFutureWatcher::paused); + QVERIFY(pausedSpy.isValid()); + + QSignalSpy suspendedSpy(&watcher, &QFutureWatcher::suspended); + QVERIFY(suspendedSpy.isValid()); + + bool pausedBeforeSuspended = false; + bool notSuspendedBeforePasused = false; + connect(&watcher, &QFutureWatcher::paused, + [&] { notSuspendedBeforePasused = (suspendedSpy.count() == 0); }); + connect(&watcher, &QFutureWatcher::suspended, + [&] { pausedBeforeSuspended = (pausedSpy.count() == 1); }); + + watcher.setFuture(iface.future()); + iface.reportSuspended(); + + // Make sure reportPaused() is ignored if the state is not paused + pausedSpy.wait(100); + QCOMPARE(pausedSpy.count(), 0); + QCOMPARE(suspendedSpy.count(), 0); + + iface.setPaused(true); + iface.reportSuspended(); + + QTRY_COMPARE(suspendedSpy.count(), 1); + QCOMPARE(pausedSpy.count(), 1); + QVERIFY(notSuspendedBeforePasused); + QVERIFY(pausedBeforeSuspended); + + iface.reportFinished(); +} + // Test that the finished state for the watcher gets // set when the finished event is delivered. // This means it will lag the finished state for the future, -- cgit v1.2.3