Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Support for dependent tasks
Cancelling a task on which others depend leads to all these other
tasks getting cancelled as well.
  • Loading branch information
nyalldawson committed Dec 5, 2016
1 parent 73e72fa commit 4291904
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 5 deletions.
15 changes: 13 additions & 2 deletions python/core/qgstaskmanager.sip
Expand Up @@ -134,6 +134,9 @@ class QgsTask : QObject

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

//! List of QgsTask objects
typedef QList< QgsTask* > QgsTaskList;

/** \ingroup core
* \class QgsTaskManager
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
Expand All @@ -159,11 +162,16 @@ class QgsTaskManager : QObject
virtual ~QgsTaskManager();

/** Adds a task to the manager. Ownership of the task is transferred
* to the manager.
* to the manager, and the task manager will be responsible for starting
* the task.
* @param task task to add
* @param dependencies list of dependent tasks. These tasks must be completed
* before task can run. If any dependent tasks are cancelled this task will also
* be cancelled. Dependent tasks must also be added to this task manager for proper
* handling of dependencies.
* @returns unique task ID
*/
long addTask( QgsTask* task /Transfer/ );
long addTask( QgsTask* task /Transfer/, const QgsTaskList& dependencies = QgsTaskList() );

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

//! Returns true if all dependencies for the specified task are satisfied
bool dependenciesSatisified( long taskId ) const;

signals:

//! Will be emitted when a task reports a progress change
Expand Down
50 changes: 48 additions & 2 deletions src/core/qgstaskmanager.cpp
Expand Up @@ -43,6 +43,11 @@ void QgsTask::start()
void QgsTask::cancel()
{
mShouldTerminate = true;
if ( mStatus == Queued || mStatus == OnHold )
{
// immediately terminate unstarted jobs
stopped();
}
}

void QgsTask::hold()
Expand Down Expand Up @@ -118,13 +123,18 @@ QgsTaskManager::~QgsTaskManager()
}
}

long QgsTaskManager::addTask( QgsTask* task )
long QgsTaskManager::addTask( QgsTask* task, const QgsTaskList& dependencies )
{
mTasks.insert( mNextTaskId, task );

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

if ( !dependencies.isEmpty() )
{
mTaskDependencies.insert( mNextTaskId, dependencies );
}

emit taskAdded( mNextTaskId );
processQueue();

Expand Down Expand Up @@ -198,6 +208,20 @@ void QgsTaskManager::cancelAll()
}
}

bool QgsTaskManager::dependenciesSatisified( long taskId ) const
{
if ( !mTaskDependencies.contains( taskId ) )
return true;

Q_FOREACH ( QgsTask* task, mTaskDependencies.value( taskId ) )
{
if ( task->status() != QgsTask::Complete )
return false;
}

return true;
}

void QgsTaskManager::taskProgressChanged( double progress )
{
QgsTask* task = qobject_cast< QgsTask* >( sender() );
Expand All @@ -219,6 +243,12 @@ void QgsTaskManager::taskStatusChanged( int status )
if ( id < 0 )
return;

if ( status == QgsTask::Terminated )
{
//recursively cancel dependant tasks
cancelDependentTasks( id );
}

emit statusChanged( id, status );
processQueue();
}
Expand Down Expand Up @@ -251,9 +281,25 @@ void QgsTaskManager::processQueue()
for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
{
QgsTask* task = it.value().task;
if ( task && task->status() == QgsTask::Queued )
if ( task && task->status() == QgsTask::Queued && dependenciesSatisified( taskId( task ) ) )
{
mTasks[ it.key()].future = QtConcurrent::run( task, &QgsTask::start );
}
}
}

void QgsTaskManager::cancelDependentTasks( long taskId )
{
QgsTask* cancelledTask = task( taskId );
for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
{
if ( it.value().contains( cancelledTask ) )
{
// found task with this dependancy

// cancel it - note that this will be recursive, so any tasks dependant
// on this one will also be cancelled
task( it.key() )->cancel();
}
}
}
18 changes: 17 additions & 1 deletion src/core/qgstaskmanager.h
Expand Up @@ -166,6 +166,9 @@ class CORE_EXPORT QgsTask : public QObject

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsTask::Flags )

//! List of QgsTask objects
typedef QList< QgsTask* > QgsTaskList;

/** \ingroup core
* \class QgsTaskManager
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
Expand Down Expand Up @@ -193,9 +196,13 @@ class CORE_EXPORT QgsTaskManager : public QObject
* to the manager, and the task manager will be responsible for starting
* the task.
* @param task task to add
* @param dependencies list of dependent tasks. These tasks must be completed
* before task can run. If any dependent tasks are cancelled this task will also
* be cancelled. Dependent tasks must also be added to this task manager for proper
* handling of dependencies.
* @returns unique task ID
*/
long addTask( QgsTask* task );
long addTask( QgsTask* task, const QgsTaskList& dependencies = QgsTaskList() );

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

//! Returns true if all dependencies for the specified task are satisfied
bool dependenciesSatisified( long taskId ) const;

signals:

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

QMap< long, TaskInfo > mTasks;
QMap< long, QgsTaskList > mTaskDependencies;

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

//! Recursively cancel dependent tasks
//! @param taskId id of terminated task to cancel any other tasks
//! which are dependent on
void cancelDependentTasks( long taskId );

};

#endif //QGSTASKMANAGER_H
1 change: 1 addition & 0 deletions src/gui/qgstaskmanagerwidget.cpp
Expand Up @@ -334,6 +334,7 @@ void QgsTaskStatusDelegate::paint( QPainter *painter, const QStyleOptionViewItem
switch ( static_cast< QgsTask::TaskStatus >( index.data().toInt() ) )
{
case QgsTask::Queued:
case QgsTask::OnHold:
icon = QgsApplication::getThemeIcon( "/mIconFieldTime.svg" );
break;
case QgsTask::Running:
Expand Down
57 changes: 57 additions & 0 deletions tests/src/core/testqgstaskmanager.cpp
Expand Up @@ -85,6 +85,7 @@ class TestQgsTaskManager : public QObject
void progressChanged();
void statusChanged();
void holdTask();
void dependancies();

private:

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

// test that cancelling tasks which have not begin immediately ends them
task.reset( new TestTask() );
task->cancel(); // Queued task
QCOMPARE( task->status(), QgsTask::Terminated );
task.reset( new TestTask() );
task->hold(); // OnHold task
task->cancel();
QCOMPARE( task->status(), QgsTask::Terminated );

// test flags
task.reset( new TestTask( "desc", QgsTask::CanReportProgress ) );
QVERIFY( !task->canCancel() );
Expand Down Expand Up @@ -351,6 +361,53 @@ void TestQgsTaskManager::holdTask()
QCOMPARE( task->status(), QgsTask::Running );
}

void TestQgsTaskManager::dependancies()
{
QgsTaskManager manager;

//test that cancelling tasks cancels all tasks which are dependant on them
TestTask* task = new TestTask();
task->hold();
TestTask* childTask = new TestTask();
childTask->hold();
TestTask* grandChildTask = new TestTask();
grandChildTask->hold();

manager.addTask( task, QgsTaskList() << childTask );
manager.addTask( childTask, QgsTaskList() << grandChildTask );
manager.addTask( grandChildTask );

grandChildTask->cancel();
QCOMPARE( childTask->status(), QgsTask::Terminated );
QCOMPARE( task->status(), QgsTask::Terminated );

// test that tasks are queued until dependancies are resolved
task = new TestTask();
childTask = new TestTask();
childTask->hold();
long taskId = manager.addTask( task, QgsTaskList() << childTask );
long childTaskId = manager.addTask( childTask );
QVERIFY( !manager.dependenciesSatisified( taskId ) );
QVERIFY( manager.dependenciesSatisified( childTaskId ) );

QCOMPARE( childTask->status(), QgsTask::OnHold );
QCOMPARE( task->status(), QgsTask::Queued );

childTask->unhold();
//wait for childTask to spin up
while ( !childTask->isActive() ) {}
QCOMPARE( childTask->status(), QgsTask::Running );
QCOMPARE( task->status(), QgsTask::Queued );
childTask->emitTaskCompleted();
//wait for childTask to complete
while ( childTask->isActive() ) {}
QVERIFY( manager.dependenciesSatisified( taskId ) );
QCOMPARE( childTask->status(), QgsTask::Complete );
//wait for task to spin up
while ( !task->isActive() ) {}
QCOMPARE( task->status(), QgsTask::Running );
}


QTEST_MAIN( TestQgsTaskManager )
#include "testqgstaskmanager.moc"

0 comments on commit 4291904

Please sign in to comment.