Skip to content

Commit 4291904

Browse files
committedDec 5, 2016
Support for dependent tasks
Cancelling a task on which others depend leads to all these other tasks getting cancelled as well.
1 parent 73e72fa commit 4291904

File tree

5 files changed

+136
-5
lines changed

5 files changed

+136
-5
lines changed
 

‎python/core/qgstaskmanager.sip

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ class QgsTask : QObject
134134

135135
QFlags<QgsTask::Flag> operator|(QgsTask::Flag f1, QFlags<QgsTask::Flag> f2);
136136

137+
//! List of QgsTask objects
138+
typedef QList< QgsTask* > QgsTaskList;
139+
137140
/** \ingroup core
138141
* \class QgsTaskManager
139142
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
@@ -159,11 +162,16 @@ class QgsTaskManager : QObject
159162
virtual ~QgsTaskManager();
160163

161164
/** Adds a task to the manager. Ownership of the task is transferred
162-
* to the manager.
165+
* to the manager, and the task manager will be responsible for starting
166+
* the task.
163167
* @param task task to add
168+
* @param dependencies list of dependent tasks. These tasks must be completed
169+
* before task can run. If any dependent tasks are cancelled this task will also
170+
* be cancelled. Dependent tasks must also be added to this task manager for proper
171+
* handling of dependencies.
164172
* @returns unique task ID
165173
*/
166-
long addTask( QgsTask* task /Transfer/ );
174+
long addTask( QgsTask* task /Transfer/, const QgsTaskList& dependencies = QgsTaskList() );
167175

168176
/** Deletes the specified task, first terminating it if it is currently
169177
* running.
@@ -201,6 +209,9 @@ class QgsTaskManager : QObject
201209
//! Instructs all tasks tracked by the manager to terminate.
202210
void cancelAll();
203211

212+
//! Returns true if all dependencies for the specified task are satisfied
213+
bool dependenciesSatisified( long taskId ) const;
214+
204215
signals:
205216

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

‎src/core/qgstaskmanager.cpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ void QgsTask::start()
4343
void QgsTask::cancel()
4444
{
4545
mShouldTerminate = true;
46+
if ( mStatus == Queued || mStatus == OnHold )
47+
{
48+
// immediately terminate unstarted jobs
49+
stopped();
50+
}
4651
}
4752

4853
void QgsTask::hold()
@@ -118,13 +123,18 @@ QgsTaskManager::~QgsTaskManager()
118123
}
119124
}
120125

121-
long QgsTaskManager::addTask( QgsTask* task )
126+
long QgsTaskManager::addTask( QgsTask* task, const QgsTaskList& dependencies )
122127
{
123128
mTasks.insert( mNextTaskId, task );
124129

125130
connect( task, SIGNAL( progressChanged( double ) ), this, SLOT( taskProgressChanged( double ) ) );
126131
connect( task, SIGNAL( statusChanged( int ) ), this, SLOT( taskStatusChanged( int ) ) );
127132

133+
if ( !dependencies.isEmpty() )
134+
{
135+
mTaskDependencies.insert( mNextTaskId, dependencies );
136+
}
137+
128138
emit taskAdded( mNextTaskId );
129139
processQueue();
130140

@@ -198,6 +208,20 @@ void QgsTaskManager::cancelAll()
198208
}
199209
}
200210

211+
bool QgsTaskManager::dependenciesSatisified( long taskId ) const
212+
{
213+
if ( !mTaskDependencies.contains( taskId ) )
214+
return true;
215+
216+
Q_FOREACH ( QgsTask* task, mTaskDependencies.value( taskId ) )
217+
{
218+
if ( task->status() != QgsTask::Complete )
219+
return false;
220+
}
221+
222+
return true;
223+
}
224+
201225
void QgsTaskManager::taskProgressChanged( double progress )
202226
{
203227
QgsTask* task = qobject_cast< QgsTask* >( sender() );
@@ -219,6 +243,12 @@ void QgsTaskManager::taskStatusChanged( int status )
219243
if ( id < 0 )
220244
return;
221245

246+
if ( status == QgsTask::Terminated )
247+
{
248+
//recursively cancel dependant tasks
249+
cancelDependentTasks( id );
250+
}
251+
222252
emit statusChanged( id, status );
223253
processQueue();
224254
}
@@ -251,9 +281,25 @@ void QgsTaskManager::processQueue()
251281
for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
252282
{
253283
QgsTask* task = it.value().task;
254-
if ( task && task->status() == QgsTask::Queued )
284+
if ( task && task->status() == QgsTask::Queued && dependenciesSatisified( taskId( task ) ) )
255285
{
256286
mTasks[ it.key()].future = QtConcurrent::run( task, &QgsTask::start );
257287
}
258288
}
259289
}
290+
291+
void QgsTaskManager::cancelDependentTasks( long taskId )
292+
{
293+
QgsTask* cancelledTask = task( taskId );
294+
for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
295+
{
296+
if ( it.value().contains( cancelledTask ) )
297+
{
298+
// found task with this dependancy
299+
300+
// cancel it - note that this will be recursive, so any tasks dependant
301+
// on this one will also be cancelled
302+
task( it.key() )->cancel();
303+
}
304+
}
305+
}

‎src/core/qgstaskmanager.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ class CORE_EXPORT QgsTask : public QObject
166166

167167
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsTask::Flags )
168168

169+
//! List of QgsTask objects
170+
typedef QList< QgsTask* > QgsTaskList;
171+
169172
/** \ingroup core
170173
* \class QgsTaskManager
171174
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
@@ -193,9 +196,13 @@ class CORE_EXPORT QgsTaskManager : public QObject
193196
* to the manager, and the task manager will be responsible for starting
194197
* the task.
195198
* @param task task to add
199+
* @param dependencies list of dependent tasks. These tasks must be completed
200+
* before task can run. If any dependent tasks are cancelled this task will also
201+
* be cancelled. Dependent tasks must also be added to this task manager for proper
202+
* handling of dependencies.
196203
* @returns unique task ID
197204
*/
198-
long addTask( QgsTask* task );
205+
long addTask( QgsTask* task, const QgsTaskList& dependencies = QgsTaskList() );
199206

200207
/** Deletes the specified task, first terminating it if it is currently
201208
* running.
@@ -233,6 +240,9 @@ class CORE_EXPORT QgsTaskManager : public QObject
233240
//! Instructs all tasks tracked by the manager to terminate.
234241
void cancelAll();
235242

243+
//! Returns true if all dependencies for the specified task are satisfied
244+
bool dependenciesSatisified( long taskId ) const;
245+
236246
signals:
237247

238248
//! Will be emitted when a task reports a progress change
@@ -272,6 +282,7 @@ class CORE_EXPORT QgsTaskManager : public QObject
272282
};
273283

274284
QMap< long, TaskInfo > mTasks;
285+
QMap< long, QgsTaskList > mTaskDependencies;
275286

276287
//! Tracks the next unique task ID
277288
long mNextTaskId;
@@ -282,6 +293,11 @@ class CORE_EXPORT QgsTaskManager : public QObject
282293
//! which are ready to go.
283294
void processQueue();
284295

296+
//! Recursively cancel dependent tasks
297+
//! @param taskId id of terminated task to cancel any other tasks
298+
//! which are dependent on
299+
void cancelDependentTasks( long taskId );
300+
285301
};
286302

287303
#endif //QGSTASKMANAGER_H

‎src/gui/qgstaskmanagerwidget.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ void QgsTaskStatusDelegate::paint( QPainter *painter, const QStyleOptionViewItem
334334
switch ( static_cast< QgsTask::TaskStatus >( index.data().toInt() ) )
335335
{
336336
case QgsTask::Queued:
337+
case QgsTask::OnHold:
337338
icon = QgsApplication::getThemeIcon( "/mIconFieldTime.svg" );
338339
break;
339340
case QgsTask::Running:

‎tests/src/core/testqgstaskmanager.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class TestQgsTaskManager : public QObject
8585
void progressChanged();
8686
void statusChanged();
8787
void holdTask();
88+
void dependancies();
8889

8990
private:
9091

@@ -150,6 +151,15 @@ void TestQgsTaskManager::task()
150151
QCOMPARE( statusSpy2.count(), 1 );
151152
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy2.last().at( 0 ).toInt() ), QgsTask::Complete );
152153

154+
// test that cancelling tasks which have not begin immediately ends them
155+
task.reset( new TestTask() );
156+
task->cancel(); // Queued task
157+
QCOMPARE( task->status(), QgsTask::Terminated );
158+
task.reset( new TestTask() );
159+
task->hold(); // OnHold task
160+
task->cancel();
161+
QCOMPARE( task->status(), QgsTask::Terminated );
162+
153163
// test flags
154164
task.reset( new TestTask( "desc", QgsTask::CanReportProgress ) );
155165
QVERIFY( !task->canCancel() );
@@ -351,6 +361,53 @@ void TestQgsTaskManager::holdTask()
351361
QCOMPARE( task->status(), QgsTask::Running );
352362
}
353363

364+
void TestQgsTaskManager::dependancies()
365+
{
366+
QgsTaskManager manager;
367+
368+
//test that cancelling tasks cancels all tasks which are dependant on them
369+
TestTask* task = new TestTask();
370+
task->hold();
371+
TestTask* childTask = new TestTask();
372+
childTask->hold();
373+
TestTask* grandChildTask = new TestTask();
374+
grandChildTask->hold();
375+
376+
manager.addTask( task, QgsTaskList() << childTask );
377+
manager.addTask( childTask, QgsTaskList() << grandChildTask );
378+
manager.addTask( grandChildTask );
379+
380+
grandChildTask->cancel();
381+
QCOMPARE( childTask->status(), QgsTask::Terminated );
382+
QCOMPARE( task->status(), QgsTask::Terminated );
383+
384+
// test that tasks are queued until dependancies are resolved
385+
task = new TestTask();
386+
childTask = new TestTask();
387+
childTask->hold();
388+
long taskId = manager.addTask( task, QgsTaskList() << childTask );
389+
long childTaskId = manager.addTask( childTask );
390+
QVERIFY( !manager.dependenciesSatisified( taskId ) );
391+
QVERIFY( manager.dependenciesSatisified( childTaskId ) );
392+
393+
QCOMPARE( childTask->status(), QgsTask::OnHold );
394+
QCOMPARE( task->status(), QgsTask::Queued );
395+
396+
childTask->unhold();
397+
//wait for childTask to spin up
398+
while ( !childTask->isActive() ) {}
399+
QCOMPARE( childTask->status(), QgsTask::Running );
400+
QCOMPARE( task->status(), QgsTask::Queued );
401+
childTask->emitTaskCompleted();
402+
//wait for childTask to complete
403+
while ( childTask->isActive() ) {}
404+
QVERIFY( manager.dependenciesSatisified( taskId ) );
405+
QCOMPARE( childTask->status(), QgsTask::Complete );
406+
//wait for task to spin up
407+
while ( !task->isActive() ) {}
408+
QCOMPARE( task->status(), QgsTask::Running );
409+
}
410+
354411

355412
QTEST_MAIN( TestQgsTaskManager )
356413
#include "testqgstaskmanager.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.