Skip to content

Commit 6021d78

Browse files
committedDec 5, 2016
Make QgsTaskManager handle threading of tasks
1 parent ebae15f commit 6021d78

File tree

5 files changed

+175
-40
lines changed

5 files changed

+175
-40
lines changed
 

‎python/core/qgstaskmanager.sip‎

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,23 @@ class QgsTask : QObject
1414
//! Status of tasks
1515
enum TaskStatus
1616
{
17+
Queued, /*!< Task is queued and has not begun */
1718
Running, /*!< Task is currently running */
1819
Complete, /*!< Task successfully completed */
1920
Terminated, /*!< Task was terminated or errored */
20-
// Paused,
21-
// Queued,
2221
};
2322

2423
/** Constructor for QgsTask.
2524
* @param description text description of task
2625
*/
2726
QgsTask( const QString& description = QString() );
2827

29-
//! Will be called when task has been terminated, either through
30-
//! user interaction or other reason (eg application exit)
31-
//! @note derived classes must ensure they call the base method
32-
virtual void terminate();
28+
//! Starts the task.
29+
void start();
30+
31+
//! Notifies the task that it should terminate.
32+
//! @see isCancelled()
33+
void terminate();
3334

3435
//! Returns true if the task is active, ie it is not complete and has
3536
//! not been terminated.
@@ -58,6 +59,11 @@ class QgsTask : QObject
5859
//! completed() or stopped()
5960
void statusChanged( int status );
6061

62+
//! Will be emitted by task to indicate its commencement.
63+
//! @note derived classes should not emit this signal directly, it will automatically
64+
//! be emitted when the task begins
65+
void begun();
66+
6167
//! Will be emitted by task to indicate its completion.
6268
//! @note derived classes should not emit this signal directly, instead they should call
6369
//! completed()
@@ -69,7 +75,7 @@ class QgsTask : QObject
6975
//! stopped()//!
7076
void taskStopped();
7177

72-
protected:
78+
public slots:
7379

7480
//! Sets the task's current progress. Should be called whenever the
7581
//! task wants to update it's progress. Calling will automatically emit the progressChanged
@@ -85,6 +91,17 @@ class QgsTask : QObject
8591
//! reason other than successful completion.
8692
//! Calling will automatically emit the statusChanged and taskStopped signals.
8793
void stopped();
94+
95+
protected:
96+
97+
//! Derived tasks must implement a run() method. This method will be called when the
98+
//! task commences (ie via calling start() ).
99+
virtual void run() = 0;
100+
101+
//! Will return true if task should terminate ASAP. Derived classes run() methods
102+
//! should periodically check this and terminate in a safe manner.
103+
bool isCancelled() const;
104+
88105
};
89106

90107
/** \ingroup core
@@ -151,6 +168,9 @@ class QgsTaskManager : QObject
151168
*/
152169
long taskId( QgsTask* task ) const;
153170

171+
//! Instructs all tasks tracked by the manager to terminate.
172+
void terminateAll();
173+
154174
signals:
155175

156176
//! Will be emitted when a task reports a progress change

‎src/core/qgstaskmanager.cpp‎

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
***************************************************************************/
1717

1818
#include "qgstaskmanager.h"
19-
#include <QMutex>
19+
#include <QtConcurrentRun>
2020

2121

2222
//
@@ -26,10 +26,24 @@
2626
QgsTask::QgsTask( const QString &name )
2727
: QObject()
2828
, mDescription( name )
29-
, mStatus( Running )
29+
, mStatus( Queued )
3030
, mProgress( 0.0 )
31+
, mShouldTerminate( false )
3132
{}
3233

34+
void QgsTask::start()
35+
{
36+
mStatus = Running;
37+
emit statusChanged( Running );
38+
emit begun();
39+
run();
40+
}
41+
42+
void QgsTask::terminate()
43+
{
44+
mShouldTerminate = true;
45+
}
46+
3347
void QgsTask::setProgress( double progress )
3448
{
3549
mProgress = progress;
@@ -74,28 +88,33 @@ QgsTaskManager::QgsTaskManager( QObject* parent )
7488

7589
QgsTaskManager::~QgsTaskManager()
7690
{
77-
QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
91+
//first tell all tasks to cancel
92+
terminateAll();
93+
94+
//then clean them up, including waiting for them to terminate
95+
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
7896
for ( ; it != mTasks.constEnd(); ++it )
7997
{
80-
cleanupAndDeleteTask( it.value() );
98+
cleanupAndDeleteTask( it.value().task );
8199
}
82100
}
83101

84102
long QgsTaskManager::addTask( QgsTask* task )
85103
{
86-
static QMutex sAddMutex( QMutex::Recursive );
87-
QMutexLocker locker( &sAddMutex );
88-
89104
mTasks.insert( mNextTaskId, task );
105+
90106
connect( task, SIGNAL( progressChanged( double ) ), this, SLOT( taskProgressChanged( double ) ) );
91107
connect( task, SIGNAL( statusChanged( int ) ), this, SLOT( taskStatusChanged( int ) ) );
108+
109+
mTasks[ mNextTaskId ].future = QtConcurrent::run( task, &QgsTask::start );
110+
92111
emit taskAdded( mNextTaskId );
93112
return mNextTaskId++;
94113
}
95114

96115
bool QgsTaskManager::deleteTask( long id )
97116
{
98-
QgsTask* task = mTasks.value( id );
117+
QgsTask* task = mTasks.value( id ).task;
99118
return deleteTask( task );
100119
}
101120

@@ -107,9 +126,9 @@ bool QgsTaskManager::deleteTask( QgsTask *task )
107126
bool result = cleanupAndDeleteTask( task );
108127

109128
// remove from internal task list
110-
for ( QMap< long, QgsTask* >::iterator it = mTasks.begin(); it != mTasks.end(); )
129+
for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); )
111130
{
112-
if ( it.value() == task )
131+
if ( it.value().task == task )
113132
it = mTasks.erase( it );
114133
else
115134
++it;
@@ -120,28 +139,46 @@ bool QgsTaskManager::deleteTask( QgsTask *task )
120139

121140
QgsTask*QgsTaskManager::task( long id ) const
122141
{
123-
return mTasks.value( id );
142+
return mTasks.value( id ).task;
124143
}
125144

126145
QList<QgsTask*> QgsTaskManager::tasks() const
127146
{
128-
return mTasks.values();
147+
QList< QgsTask* > list;
148+
for ( QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin(); it != mTasks.constEnd(); ++it )
149+
{
150+
list << it.value().task;
151+
}
152+
return list;
129153
}
130154

131155
long QgsTaskManager::taskId( QgsTask *task ) const
132156
{
133157
if ( !task )
134158
return -1;
135159

136-
QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
160+
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
137161
for ( ; it != mTasks.constEnd(); ++it )
138162
{
139-
if ( it.value() == task )
163+
if ( it.value().task == task )
140164
return it.key();
141165
}
142166
return -1;
143167
}
144168

169+
void QgsTaskManager::terminateAll()
170+
{
171+
QMap< long, TaskInfo >::iterator it = mTasks.begin();
172+
for ( ; it != mTasks.end(); ++it )
173+
{
174+
QgsTask* task = it.value().task;
175+
if ( task->isActive() )
176+
{
177+
task->terminate();
178+
}
179+
}
180+
}
181+
145182
void QgsTaskManager::taskProgressChanged( double progress )
146183
{
147184
QgsTask* task = qobject_cast< QgsTask* >( sender() );
@@ -174,6 +211,17 @@ bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
174211
if ( task->isActive() )
175212
task->terminate();
176213

214+
// wait for task to terminate
215+
QMap< long, TaskInfo >::iterator it = mTasks.begin();
216+
for ( ; it != mTasks.end(); ++it )
217+
{
218+
if ( it.value().task == task )
219+
{
220+
it.value().future.waitForFinished();
221+
break;
222+
}
223+
}
224+
177225
emit taskAboutToBeDeleted( taskId( task ) );
178226

179227
task->deleteLater();

‎src/core/qgstaskmanager.h‎

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
#include <QObject>
2222
#include <QMap>
2323
#include <QAbstractItemModel>
24+
#include <QFuture>
2425

2526
/** \ingroup core
2627
* \class QgsTask
27-
* \brief Interface class for long running background tasks which will be handled by a QgsTaskManager
28+
* \brief Interface class for long running background tasks. Tasks can be controlled directly,
29+
* or added to a QgsTaskManager for automatic management.
2830
* \note Added in version 2.16
2931
*/
3032
class CORE_EXPORT QgsTask : public QObject
@@ -36,25 +38,23 @@ class CORE_EXPORT QgsTask : public QObject
3638
//! Status of tasks
3739
enum TaskStatus
3840
{
41+
Queued, /*!< Task is queued and has not begun */
3942
Running, /*!< Task is currently running */
4043
Complete, /*!< Task successfully completed */
4144
Terminated, /*!< Task was terminated or errored */
42-
// Paused,
43-
// Queued,
4445
};
4546

4647
/** Constructor for QgsTask.
4748
* @param description text description of task
4849
*/
4950
QgsTask( const QString& description = QString() );
5051

51-
//! Will be called when task has been terminated, either through
52-
//! user interaction or other reason (eg application exit)
53-
//! @note derived classes must ensure they call the base method
54-
virtual void terminate()
55-
{
56-
stopped();
57-
}
52+
//! Starts the task.
53+
void start();
54+
55+
//! Notifies the task that it should terminate.
56+
//! @see isCancelled()
57+
void terminate();
5858

5959
//! Returns true if the task is active, ie it is not complete and has
6060
//! not been terminated.
@@ -83,6 +83,11 @@ class CORE_EXPORT QgsTask : public QObject
8383
//! completed() or stopped()
8484
void statusChanged( int status );
8585

86+
//! Will be emitted by task to indicate its commencement.
87+
//! @note derived classes should not emit this signal directly, it will automatically
88+
//! be emitted when the task begins
89+
void begun();
90+
8691
//! Will be emitted by task to indicate its completion.
8792
//! @note derived classes should not emit this signal directly, instead they should call
8893
//! completed()
@@ -94,7 +99,7 @@ class CORE_EXPORT QgsTask : public QObject
9499
//! stopped()//!
95100
void taskStopped();
96101

97-
protected:
102+
public slots:
98103

99104
//! Sets the task's current progress. Should be called whenever the
100105
//! task wants to update it's progress. Calling will automatically emit the progressChanged
@@ -111,11 +116,22 @@ class CORE_EXPORT QgsTask : public QObject
111116
//! Calling will automatically emit the statusChanged and taskStopped signals.
112117
void stopped();
113118

119+
protected:
120+
121+
//! Derived tasks must implement a run() method. This method will be called when the
122+
//! task commences (ie via calling start() ).
123+
virtual void run() = 0;
124+
125+
//! Will return true if task should terminate ASAP. Derived classes run() methods
126+
//! should periodically check this and terminate in a safe manner.
127+
bool isCancelled() const { return mShouldTerminate; }
128+
114129
private:
115130

116131
QString mDescription;
117132
TaskStatus mStatus;
118133
double mProgress;
134+
bool mShouldTerminate;
119135

120136
};
121137

@@ -143,7 +159,8 @@ class CORE_EXPORT QgsTaskManager : public QObject
143159
virtual ~QgsTaskManager();
144160

145161
/** Adds a task to the manager. Ownership of the task is transferred
146-
* to the manager.
162+
* to the manager, and the task manager will be responsible for starting
163+
* the task.
147164
* @param task task to add
148165
* @returns unique task ID
149166
*/
@@ -182,6 +199,9 @@ class CORE_EXPORT QgsTaskManager : public QObject
182199
*/
183200
long taskId( QgsTask* task ) const;
184201

202+
//! Instructs all tasks tracked by the manager to terminate.
203+
void terminateAll();
204+
185205
signals:
186206

187207
//! Will be emitted when a task reports a progress change
@@ -210,7 +230,17 @@ class CORE_EXPORT QgsTaskManager : public QObject
210230
private:
211231

212232
static QgsTaskManager *sInstance;
213-
QMap< long, QgsTask* > mTasks;
233+
234+
struct TaskInfo
235+
{
236+
TaskInfo( QgsTask* task = nullptr )
237+
: task( task )
238+
{}
239+
QgsTask* task;
240+
QFuture< void > future;
241+
};
242+
243+
QMap< long, TaskInfo > mTasks;
214244

215245
//! Tracks the next unique task ID
216246
long mNextTaskId;

‎src/gui/qgstaskmanagerwidget.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ void QgsTaskStatusDelegate::paint( QPainter *painter, const QStyleOptionViewItem
321321
QIcon icon;
322322
switch ( static_cast< QgsTask::TaskStatus >( index.data().toInt() ) )
323323
{
324+
case QgsTask::Queued:
325+
icon = QgsApplication::getThemeIcon( "/mIconFieldTime.svg" );
326+
break;
324327
case QgsTask::Running:
325328
icon = QgsApplication::getThemeIcon( "/mActionRefresh.png" );
326329
break;

‎tests/src/core/testqgstaskmanager.cpp‎

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,21 @@ class TestTask : public QgsTask
2626

2727
public:
2828

29-
TestTask( const QString& desc = QString() ) : QgsTask( desc ) {}
29+
TestTask( const QString& desc = QString() ) : QgsTask( desc ), runCalled( false ) {}
3030

3131
void emitProgressChanged( double progress ) { setProgress( progress ); }
3232
void emitTaskStopped() { stopped(); }
3333
void emitTaskCompleted() { completed(); }
3434

35+
bool runCalled;
36+
37+
protected:
38+
39+
void run() override
40+
{
41+
runCalled = true;
42+
}
43+
3544
};
3645

3746
class TestTerminationTask : public TestTask
@@ -45,6 +54,13 @@ class TestTerminationTask : public TestTask
4554
//make sure task has been terminated by manager prior to deletion
4655
Q_ASSERT( status() == QgsTask::Terminated );
4756
}
57+
58+
protected:
59+
60+
void run() override
61+
{
62+
QTest::qSleep( 1000 );
63+
}
4864
};
4965

5066

@@ -93,18 +109,28 @@ void TestQgsTaskManager::cleanup()
93109
void TestQgsTaskManager::task()
94110
{
95111
QScopedPointer< TestTask > task( new TestTask( "desc" ) );
96-
QCOMPARE( task->status(), QgsTask::Running );
112+
QCOMPARE( task->status(), QgsTask::Queued );
97113
QCOMPARE( task->description(), QString( "desc" ) );
114+
QVERIFY( !task->isActive() );
115+
116+
QSignalSpy startedSpy( task.data(), SIGNAL( begun() ) );
117+
QSignalSpy statusSpy( task.data(), SIGNAL( statusChanged( int ) ) );
118+
119+
task->start();
120+
QCOMPARE( task->status(), QgsTask::Running );
98121
QVERIFY( task->isActive() );
122+
QVERIFY( task->runCalled );
123+
QCOMPARE( startedSpy.count(), 1 );
124+
QCOMPARE( statusSpy.count(), 1 );
125+
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy.last().at( 0 ).toInt() ), QgsTask::Running );
99126

100127
//test that calling stopped sets correct state
101128
QSignalSpy stoppedSpy( task.data(), SIGNAL( taskStopped() ) );
102-
QSignalSpy statusSpy( task.data(), SIGNAL( statusChanged( int ) ) );
103129
task->emitTaskStopped();
104130
QCOMPARE( task->status(), QgsTask::Terminated );
105131
QVERIFY( !task->isActive() );
106132
QCOMPARE( stoppedSpy.count(), 1 );
107-
QCOMPARE( statusSpy.count(), 1 );
133+
QCOMPARE( statusSpy.count(), 2 );
108134
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy.last().at( 0 ).toInt() ), QgsTask::Terminated );
109135

110136
//test that calling completed sets correct state
@@ -211,6 +237,9 @@ void TestQgsTaskManager::taskTerminationBeforeDelete()
211237
TestTask* task = new TestTerminationTask();
212238
manager->addTask( task );
213239

240+
//SHOULD NOT BE NEEDED...
241+
task->start();
242+
214243
// if task is not terminated assert will trip
215244
delete manager;
216245
QApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
@@ -273,13 +302,18 @@ void TestQgsTaskManager::statusChanged()
273302

274303
QSignalSpy spy( &manager, SIGNAL( statusChanged( long, int ) ) );
275304

276-
task->emitTaskStopped();
305+
task->start();
277306
QCOMPARE( spy.count(), 1 );
278307
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
308+
QCOMPARE( static_cast< QgsTask::TaskStatus >( spy.last().at( 1 ).toInt() ), QgsTask::Running );
309+
310+
task->emitTaskStopped();
311+
QCOMPARE( spy.count(), 2 );
312+
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
279313
QCOMPARE( static_cast< QgsTask::TaskStatus >( spy.last().at( 1 ).toInt() ), QgsTask::Terminated );
280314

281315
task2->emitTaskCompleted();
282-
QCOMPARE( spy.count(), 2 );
316+
QCOMPARE( spy.count(), 3 );
283317
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
284318
QCOMPARE( static_cast< QgsTask::TaskStatus >( spy.last().at( 1 ).toInt() ), QgsTask::Complete );
285319
}

0 commit comments

Comments
 (0)
Please sign in to comment.