QgsTasks can have subtasks

Now, a QgsTask can have subtask QgsTasks set by calling
QgsTask::addSubTask. Sub tasks can have their own set of
dependent tasks.

Subtasks are not visible to users, and users only see the overall
progress and status of the parent task.

This allows creation of tasks which are themselves built off
many smaller component tasks. The task manager will still handle
firing up and scheduling the subtasks, so eg subtasks can run
in parallel (if their dependancies allow this).

Subtasks can themselves have subtasks.

This change is designed to allow the processing concept of
algorithms and modeller algorithms to be translatable
directly to the task manager architecture.
This commit is contained in:
Nyall Dawson 2016-11-25 18:15:08 +10:00
parent 3999a3710f
commit 32a9e1e9d6
5 changed files with 812 additions and 111 deletions

View File

@ -96,10 +96,10 @@ class QgsTask : QObject
void start();
/**
* Notifies the task that it should terminate. Calling this is not gauranteed
* Notifies the task that it should terminate. Calling this is not guaranteed
* to immediately end the task, rather it sets the isCancelled() flag which
* task subclasses can check and terminate their operations at an appropriate
* time.
* time. Any subtasks owned by this task will also be cancelled.
* @see isCancelled()
*/
void cancel();
@ -121,6 +121,35 @@ class QgsTask : QObject
*/
void unhold();
//! Controls how subtasks relate to their parent task
enum SubTaskDependency
{
SubTaskIndependent, //!< Subtask is independent of the parent, and can run before, after or at the same time as the parent.
ParentDependsOnSubTask, //!< Subtask must complete before parent can begin
};
/**
* Adds a subtask to this task.
*
* Subtasks allow a single task to be created which
* consists of multiple smaller tasks. Subtasks are not visible or indepedently
* controllable by users. Ownership of the subtask is transferred.
* Subtasks can have an optional list of dependant tasks, which must be completed
* before the subtask can begin. By default subtasks are considered independent
* of the parent task, ie they can be run either before, after, or at the same
* time as the parent task. This behaviour can be overriden through the subTaskDependency
* argument.
*
* The parent task must be added to a QgsTaskManager for subtasks to be utilised.
* Subtasks should not be added manually to a QgsTaskManager, rather, only the parent
* task should be added to the manager.
*
* Subtasks can be nested, ie a subtask can legally be a parent task itself with
* its own set of subtasks.
*/
void addSubTask( QgsTask* subTask /Transfer/, const QgsTaskList& dependencies = QgsTaskList(),
SubTaskDependency subTaskDependency = SubTaskIndependent );
signals:
/**
@ -258,17 +287,42 @@ class QgsTaskManager : QObject
virtual ~QgsTaskManager();
/**
* Definition of a task for inclusion within a task bundle.
*/
struct TaskDefinition
{
/**
* Constructor for TaskDefinition. Ownership of the task is transferred to the definition.
*/
TaskDefinition( QgsTask* task, QgsTaskList dependencies = QgsTaskList() );
//! Task
QgsTask* task;
/**
* List of dependencies which must be completed before task can run.
* 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 the task manager for proper handling of dependencies.
*/
QgsTaskList dependencies;
};
/** Adds a task to the manager. Ownership of the task is transferred
* 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/, const QgsTaskList& dependencies = QgsTaskList() );
long addTask( QgsTask* task /Transfer/ );
/**
* Adds a task to the manager, using a full task definition (including dependancy
* handling). Ownership of the task is transferred to the manager, and the task
* manager will be responsible for starting the task.
* @returns unique task ID
*/
long addTask( const TaskDefinition& task /Transfer/ );
/** Returns the task with matching ID.
* @param id task ID

View File

@ -29,13 +29,24 @@ QgsTask::QgsTask( const QString &name, const Flags& flags )
, mFlags( flags )
, mDescription( name )
, mStatus( Queued )
, mOverallStatus( Queued )
, mProgress( 0.0 )
, mTotalProgress( 0.0 )
, mShouldTerminate( false )
{}
QgsTask::~QgsTask()
{
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
delete subTask.task;
}
}
void QgsTask::start()
{
mStatus = Running;
mOverallStatus = Running;
emit statusChanged( Running );
emit begun();
TaskResult result = run();
@ -65,6 +76,15 @@ void QgsTask::cancel()
// immediately terminate unstarted jobs
terminated();
}
else if ( mStatus == Terminated )
{
processSubTasksForTermination();
}
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
subTask.task->cancel();
}
}
void QgsTask::hold()
@ -72,7 +92,12 @@ void QgsTask::hold()
if ( mStatus == Queued )
{
mStatus = OnHold;
emit statusChanged( OnHold );
processSubTasksForHold();
}
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
subTask.task->hold();
}
}
@ -81,28 +106,167 @@ void QgsTask::unhold()
if ( mStatus == OnHold )
{
mStatus = Queued;
mOverallStatus = Queued;
emit statusChanged( Queued );
}
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
subTask.task->unhold();
}
}
void QgsTask::addSubTask( QgsTask* subTask, const QgsTaskList& dependencies,
SubTaskDependency subTaskDependency )
{
mSubTasks << SubTask( subTask, dependencies, subTaskDependency );
connect( subTask, &QgsTask::progressChanged, this, [=] { setProgress( mProgress ); } );
connect( subTask, &QgsTask::statusChanged, this, &QgsTask::subTaskStatusChanged );
}
void QgsTask::subTaskStatusChanged( int status )
{
QgsTask* subTask = qobject_cast< QgsTask* >( sender() );
if ( !subTask )
return;
if ( status == Running && mStatus == Queued )
{
mOverallStatus = Running;
}
else if ( status == Complete && mStatus == Complete )
{
//check again if all subtasks are complete
processSubTasksForCompletion();
}
else if (( status == Complete || status == Terminated ) && mStatus == Terminated )
{
//check again if all subtasks are terminated
processSubTasksForTermination();
}
else if (( status == Complete || status == Terminated || status == OnHold ) && mStatus == OnHold )
{
processSubTasksForHold();
}
else if ( status == Terminated )
{
//uh oh...
cancel();
}
}
void QgsTask::setProgress( double progress )
{
mProgress = progress;
emit progressChanged( progress );
if ( !mSubTasks.isEmpty() )
{
// calculate total progress including subtasks
double totalProgress = 0.0;
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
if ( subTask.task->status() == QgsTask::Complete )
{
totalProgress += 100.0;
}
else
{
totalProgress += subTask.task->progress();
}
}
progress = ( progress + totalProgress ) / ( mSubTasks.count() + 1 );
}
mTotalProgress = progress;
emit progressChanged( mTotalProgress );
}
void QgsTask::completed()
{
mStatus = Complete;
emit statusChanged( Complete );
emit taskCompleted();
processSubTasksForCompletion();
}
void QgsTask::processSubTasksForCompletion()
{
bool subTasksCompleted = true;
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
if ( subTask.task->status() != Complete )
{
subTasksCompleted = false;
break;
}
}
if ( mStatus == Complete && subTasksCompleted )
{
mOverallStatus = Complete;
setProgress( 100.0 );
emit statusChanged( Complete );
emit taskCompleted();
}
else if ( mStatus == Complete )
{
// defer completion until all subtasks are complete
mOverallStatus = Running;
}
}
void QgsTask::processSubTasksForTermination()
{
bool subTasksTerminated = true;
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
if ( subTask.task->status() != Terminated && subTask.task->status() != Complete )
{
subTasksTerminated = false;
break;
}
}
if ( mStatus == Terminated && subTasksTerminated && mOverallStatus != Terminated )
{
mOverallStatus = Terminated;
emit statusChanged( Terminated );
emit taskTerminated();
}
else if ( mStatus == Terminated && !subTasksTerminated )
{
// defer termination until all subtasks are terminated (or complete)
mOverallStatus = Running;
}
}
void QgsTask::processSubTasksForHold()
{
bool subTasksRunning = false;
Q_FOREACH ( const SubTask& subTask, mSubTasks )
{
if ( subTask.task->status() == Running )
{
subTasksRunning = true;
break;
}
}
if ( mStatus == OnHold && !subTasksRunning && mOverallStatus != OnHold )
{
mOverallStatus = OnHold;
emit statusChanged( OnHold );
}
else if ( mStatus == OnHold && subTasksRunning )
{
// defer hold until all subtasks finish running
mOverallStatus = Running;
}
}
void QgsTask::terminated()
{
mStatus = Terminated;
emit statusChanged( Terminated );
emit taskTerminated();
processSubTasksForTermination();
}
@ -126,38 +290,81 @@ QgsTaskManager::~QgsTaskManager()
//then clean them up, including waiting for them to terminate
mTaskMutex->lock();
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
Q_FOREACH ( QgsTask* task, mParentTasks )
{
cleanupAndDeleteTask( it.value().task );
cleanupAndDeleteTask( task );
}
mTaskMutex->unlock();
delete mTaskMutex;
}
long QgsTaskManager::addTask( QgsTask* task, const QgsTaskList& dependencies )
long QgsTaskManager::addTask( QgsTask* task )
{
return addTaskPrivate( task, QgsTaskList(), false );
}
long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition& definition )
{
return addTaskPrivate( definition.task,
definition.dependencies,
false );
}
long QgsTaskManager::addTaskPrivate( QgsTask* task, QgsTaskList dependencies, bool isSubTask )
{
QMutexLocker ml( mTaskMutex );
mTasks.insert( mNextTaskId, task );
connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
long taskId = mNextTaskId++;
mTasks.insert( taskId, task );
if ( isSubTask )
mSubTasks << task;
else
mParentTasks << task;
connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
if ( !isSubTask )
{
connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
}
// add all subtasks, must be done before dependency resolution
Q_FOREACH ( const QgsTask::SubTask& subTask, task->mSubTasks )
{
switch ( subTask.dependency )
{
case QgsTask::ParentDependsOnSubTask:
dependencies << subTask.task;
break;
case QgsTask::SubTaskIndependent:
//nothing
break;
}
//recursively add sub tasks
addTaskPrivate( subTask.task, subTask.dependencies, true );
}
if ( !dependencies.isEmpty() )
{
mTaskDependencies.insert( mNextTaskId, dependencies );
mTaskDependencies.insert( taskId, dependencies );
}
if ( hasCircularDependencies( mNextTaskId ) )
if ( hasCircularDependencies( taskId ) )
{
task->cancel();
}
emit taskAdded( mNextTaskId );
processQueue();
if ( !isSubTask )
{
emit taskAdded( taskId );
processQueue();
}
return mNextTaskId++;
return taskId;
}
QgsTask* QgsTaskManager::task( long id ) const
@ -170,11 +377,8 @@ QList<QgsTask*> QgsTaskManager::tasks() const
{
QMutexLocker ml( mTaskMutex );
QList< QgsTask* > list;
for ( QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin(); it != mTasks.constEnd(); ++it )
{
list << it.value().task;
}
return list;
return mParentTasks.toList();
}
long QgsTaskManager::taskId( QgsTask *task ) const
@ -197,10 +401,8 @@ long QgsTaskManager::taskId( QgsTask *task ) const
void QgsTaskManager::cancelAll()
{
QMutexLocker ml( mTaskMutex );
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
Q_FOREACH ( QgsTask* task, mParentTasks )
{
QgsTask* task = it.value().task;
if ( task->isActive() )
{
task->cancel();
@ -290,15 +492,16 @@ QStringList QgsTaskManager::dependentLayers( long taskId ) const
QList<QgsTask*> QgsTaskManager::activeTasks() const
{
QMutexLocker ml( mTaskMutex );
QList< QgsTask* > taskList = mActiveTasks;
taskList.detach();
return taskList;
QSet< QgsTask* > activeTasks = mActiveTasks;
activeTasks.intersect( mParentTasks );
return activeTasks.toList();
}
int QgsTaskManager::countActiveTasks() const
{
QMutexLocker ml( mTaskMutex );
return mActiveTasks.count();
QSet< QgsTask* > tasks = mActiveTasks;
return tasks.intersect( mParentTasks ).count();
}
void QgsTaskManager::taskProgressChanged( double progress )
@ -312,7 +515,8 @@ void QgsTaskManager::taskProgressChanged( double progress )
return;
emit progressChanged( id, progress );
if ( mActiveTasks.count() == 1 )
if ( countActiveTasks() == 1 )
{
emit finalTaskProgressChanged( progress );
}
@ -340,7 +544,11 @@ void QgsTaskManager::taskStatusChanged( int status )
cancelDependentTasks( id );
}
emit statusChanged( id, status );
if ( !mSubTasks.contains( task ) )
{
// don't emit status changed for subtasks
emit statusChanged( id, status );
}
processQueue();
}
@ -370,6 +578,7 @@ void QgsTaskManager::layersWillBeRemoved( const QStringList& layerIds )
}
}
bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
{
if ( !task )
@ -396,17 +605,17 @@ bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
void QgsTaskManager::processQueue()
{
QMutexLocker ml( mTaskMutex );
int prevActiveCount = mActiveTasks.count();
int prevActiveCount = countActiveTasks();
mActiveTasks.clear();
for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
{
QgsTask* task = it.value().task;
if ( task && task->status() == QgsTask::Queued && dependenciesSatisified( taskId( task ) ) )
if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisified( taskId( task ) ) )
{
mTasks[ it.key()].future = QtConcurrent::run( task, &QgsTask::start );
}
if ( task && ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated ) )
if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
{
mActiveTasks << task;
}
@ -416,9 +625,10 @@ void QgsTaskManager::processQueue()
{
emit allTasksFinished();
}
if ( prevActiveCount != mActiveTasks.count() )
int newActiveCount = countActiveTasks();
if ( prevActiveCount != newActiveCount )
{
emit countActiveTasksChanged( mActiveTasks.count() );
emit countActiveTasksChanged( newActiveCount );
}
}

View File

@ -22,6 +22,11 @@
#include <QMap>
#include <QFuture>
class QgsTask;
//! List of QgsTask objects
typedef QList< QgsTask* > QgsTaskList;
/**
* \ingroup core
* \class QgsTask
@ -78,6 +83,8 @@ class CORE_EXPORT QgsTask : public QObject
*/
QgsTask( const QString& description = QString(), const Flags& flags = AllFlags );
~QgsTask();
/**
* Returns the flags associated with the task.
*/
@ -92,12 +99,12 @@ class CORE_EXPORT QgsTask : public QObject
* Returns true if the task is active, ie it is not complete and has
* not been cancelled.
*/
bool isActive() const { return mStatus == Running; }
bool isActive() const { return mOverallStatus == Running; }
/**
* Returns the current task status.
*/
TaskStatus status() const { return mStatus; }
TaskStatus status() const { return mOverallStatus; }
/**
* Returns the task's description.
@ -107,7 +114,7 @@ class CORE_EXPORT QgsTask : public QObject
/**
* Returns the task's progress (between 0.0 and 100.0)
*/
double progress() const { return mProgress; }
double progress() const { return mTotalProgress; }
/**
* Starts the task. Should only be called for tasks which are not being
@ -118,10 +125,10 @@ class CORE_EXPORT QgsTask : public QObject
void start();
/**
* Notifies the task that it should terminate. Calling this is not gauranteed
* Notifies the task that it should terminate. Calling this is not guaranteed
* to immediately end the task, rather it sets the isCancelled() flag which
* task subclasses can check and terminate their operations at an appropriate
* time.
* time. Any subtasks owned by this task will also be cancelled.
* @see isCancelled()
*/
void cancel();
@ -143,6 +150,36 @@ class CORE_EXPORT QgsTask : public QObject
*/
void unhold();
//! Controls how subtasks relate to their parent task
enum SubTaskDependency
{
SubTaskIndependent = 0, //!< Subtask is independent of the parent, and can run before, after or at the same time as the parent.
ParentDependsOnSubTask, //!< Subtask must complete before parent can begin
};
/**
* Adds a subtask to this task.
*
* Subtasks allow a single task to be created which
* consists of multiple smaller tasks. Subtasks are not visible or indepedently
* controllable by users. Ownership of the subtask is transferred.
* Subtasks can have an optional list of dependant tasks, which must be completed
* before the subtask can begin. By default subtasks are considered independent
* of the parent task, ie they can be run either before, after, or at the same
* time as the parent task. This behaviour can be overriden through the subTaskDependency
* argument. Note that subtasks should NEVER be dependent on their parent task, and violating
* this constraint will prevent the task from completing successfully.
*
* The parent task must be added to a QgsTaskManager for subtasks to be utilised.
* Subtasks should not be added manually to a QgsTaskManager, rather, only the parent
* task should be added to the manager.
*
* Subtasks can be nested, ie a subtask can legally be a parent task itself with
* its own set of subtasks.
*/
void addSubTask( QgsTask* subTask, const QgsTaskList& dependencies = QgsTaskList(),
SubTaskDependency subTaskDependency = SubTaskIndependent );
signals:
/**
@ -184,6 +221,8 @@ class CORE_EXPORT QgsTask : public QObject
*/
void taskTerminated();
void subTaskComplete();
protected:
/**
@ -253,22 +292,50 @@ class CORE_EXPORT QgsTask : public QObject
*/
void setProgress( double progress );
private slots:
void subTaskStatusChanged( int status );
private:
Flags mFlags;
QString mDescription;
//! Status of this (parent) task alone
TaskStatus mStatus;
//! Status of this task and all subtasks
TaskStatus mOverallStatus;
//! Progress of this (parent) task alone
double mProgress;
//! Overall progress of this task and all subtasks
double mTotalProgress;
bool mShouldTerminate;
struct SubTask
{
SubTask( QgsTask* task, QgsTaskList dependencies, SubTaskDependency dependency )
: task( task )
, dependencies( dependencies )
, dependency( dependency )
{}
QgsTask* task;
QgsTaskList dependencies;
SubTaskDependency dependency;
};
QList< SubTask > mSubTasks;
void processSubTasksForCompletion();
void processSubTasksForTermination();
void processSubTasksForHold();
friend class QgsTaskManager;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsTask::Flags )
//! List of QgsTask objects
typedef QList< QgsTask* > QgsTaskList;
/** \ingroup core
* \class QgsTaskManager
@ -289,17 +356,46 @@ class CORE_EXPORT QgsTaskManager : public QObject
virtual ~QgsTaskManager();
/**
* Definition of a task for inclusion in the manager.
*/
struct TaskDefinition
{
/**
* Constructor for TaskDefinition.
*/
TaskDefinition( QgsTask* task, QgsTaskList dependencies = QgsTaskList() )
: task( task )
, dependencies( dependencies )
{}
//! Task
QgsTask* task;
/**
* List of dependencies which must be completed before task can run.
* 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 the task manager for proper handling of dependencies.
*/
QgsTaskList dependencies;
};
/** Adds a task to the manager. Ownership of the task is transferred
* 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, const QgsTaskList& dependencies = QgsTaskList() );
long addTask( QgsTask* task );
/**
* Adds a task to the manager, using a full task definition (including dependancy
* handling). Ownership of the task is transferred to the manager, and the task
* manager will be responsible for starting the task.
* @returns unique task ID
*/
long addTask( const TaskDefinition& task );
/** Returns the task with matching ID.
* @param id task ID
@ -312,7 +408,7 @@ class CORE_EXPORT QgsTaskManager : public QObject
QList<QgsTask*> tasks() const;
//! Returns the number of tasks tracked by the manager.
int count() const { return mTasks.count(); }
int count() const { return mParentTasks.count(); }
/** Returns the unique task ID corresponding to a task managed by the class.
* @param task task to find
@ -418,8 +514,16 @@ class CORE_EXPORT QgsTaskManager : public QObject
//! Tracks the next unique task ID
long mNextTaskId;
//! List of active (queued or running) tasks
QList< QgsTask* > mActiveTasks;
//! List of active (queued or running) tasks. Includes subtasks.
QSet< QgsTask* > mActiveTasks;
//! List of parent tasks
QSet< QgsTask* > mParentTasks;
//! List of subtasks
QSet< QgsTask* > mSubTasks;
long addTaskPrivate( QgsTask* task,
QgsTaskList dependencies,
bool isSubTask );
bool cleanupAndDeleteTask( QgsTask* task );
@ -434,6 +538,7 @@ class CORE_EXPORT QgsTaskManager : public QObject
bool resolveDependencies( long firstTaskId, long currentTaskId, QSet< long >& results ) const;
};
#endif //QGSTASKMANAGER_H

View File

@ -76,6 +76,7 @@ QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *pare
}
connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
// not right - should be completion
connect( mManager, &QgsTaskManager::taskAboutToBeDeleted, this, &QgsTaskManagerModel::taskDeleted );
connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
@ -133,31 +134,33 @@ QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
return QVariant();
QgsTask* task = indexToTask( index );
if ( !task )
return QVariant();
switch ( role )
if ( task )
{
case Qt::DisplayRole:
case Qt::EditRole:
switch ( index.column() )
{
case Description:
return task->description();
case Progress:
return task->progress();
case Status:
return static_cast<int>( task->status() );
default:
return QVariant();
}
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
switch ( index.column() )
{
case Description:
return task->description();
case Progress:
return task->progress();
case Status:
return static_cast<int>( task->status() );
default:
return QVariant();
}
case StatusRole:
return static_cast<int>( task->status() );
case StatusRole:
return static_cast<int>( task->status() );
default:
return QVariant();
default:
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
@ -171,8 +174,8 @@ Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
if ( index.column() == Status )
{
if ( static_cast< QgsTask::TaskStatus >( data( index, StatusRole ).toInt() ) == QgsTask::Running )
flags = flags | Qt::ItemIsEditable;
//if ( static_cast< QgsTask::TaskStatus >( data( index, StatusRole ).toInt() ) == QgsTask::Running )
flags = flags | Qt::ItemIsEditable;
}
return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
@ -204,7 +207,8 @@ bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &val
void QgsTaskManagerModel::taskAdded( long id )
{
beginInsertRows( QModelIndex(), mRowToTaskIdMap.count(), mRowToTaskIdMap.count() );
beginInsertRows( QModelIndex(), mRowToTaskIdMap.count(),
mRowToTaskIdMap.count() );
mRowToTaskIdMap.insert( mRowToTaskIdMap.count(), id );
endInsertRows();
}
@ -256,8 +260,11 @@ QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
if ( !index.isValid() || index.parent().isValid() )
return nullptr;
long id = mRowToTaskIdMap.value( index.row() );
return mManager->task( id );
long id = mRowToTaskIdMap.value( index.row(), -1 );
if ( id >= 0 )
return mManager->task( id );
else
return nullptr;
}
int QgsTaskManagerModel::idToRow( long id ) const
@ -297,7 +304,7 @@ void QgsProgressBarDelegate::paint( QPainter *painter, const QStyleOptionViewIte
int progress = index.data().toInt();
QStyleOptionProgressBarV2 progressBarOption;
QStyleOptionProgressBar progressBarOption;
progressBarOption.state = option.state;
progressBarOption.rect = option.rect;
progressBarOption.rect.setTop( option.rect.top() + 1 );

View File

@ -160,6 +160,7 @@ class TestQgsTaskManager : public QObject
void task();
void taskResult();
void taskFinished();
void subTask();
void addTask();
//void taskTerminationBeforeDelete();
void taskId();
@ -170,6 +171,7 @@ class TestQgsTaskManager : public QObject
void holdTask();
void dependancies();
void layerDependencies();
void managerWithSubTasks();
};
@ -309,9 +311,9 @@ void TestQgsTaskManager::addTask()
QCOMPARE( manager.tasks().count(), 2 );
QCOMPARE( manager.count(), 2 );
QCOMPARE( manager.task( 0L ), task );
QCOMPARE( manager.tasks().at( 0 ), task );
QVERIFY( manager.tasks().contains( task ) );
QCOMPARE( manager.task( 1L ), task2 );
QCOMPARE( manager.tasks().at( 1 ), task2 );
QVERIFY( manager.tasks().contains( task2 ) );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
@ -330,7 +332,10 @@ void TestQgsTaskManager::taskTerminationBeforeDelete()
// wait till task spins up
while ( !task->isActive() )
{}
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
// if task is not terminated assert will trip
delete manager;
@ -349,11 +354,15 @@ void TestQgsTaskManager::taskFinished()
task->desiredResult = QgsTask::ResultSuccess;
manager.addTask( task );
while ( task->status() == QgsTask::Running
|| task->status() == QgsTask::Queued ) { }
|| task->status() == QgsTask::Queued )
{
QCoreApplication::processEvents();
}
while ( manager.countActiveTasks() > 0 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task->resultObtained, QgsTask::ResultSuccess );
task = new FinishTask();
@ -366,9 +375,139 @@ void TestQgsTaskManager::taskFinished()
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task->resultObtained, QgsTask::ResultFail );
}
void TestQgsTaskManager::subTask()
{
// parent with one subtask
TestTask* parent = new TestTask();
QPointer<TestTask> subTask( new TestTask() );
parent->addSubTask( subTask );
// subtask should be deleted with parent
delete parent;
QVERIFY( !subTask.data() );
// parent with grand children
parent = new TestTask();
subTask = new TestTask();
QPointer< TestTask> subsubTask( new TestTask() );
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
delete parent;
QVERIFY( !subTask.data() );
QVERIFY( !subsubTask.data() );
// test parent task progress
parent = new TestTask();
subTask = new TestTask();
QPointer< TestTask > subTask2( new TestTask() );
parent->addSubTask( subTask );
parent->addSubTask( subTask2 );
// test progress calculation
QSignalSpy spy( parent, &QgsTask::progressChanged );
parent->emitProgressChanged( 50 );
QCOMPARE( qRound( parent->progress() ), 17 );
QCOMPARE( spy.count(), 1 );
QCOMPARE( qRound( spy.last().at( 0 ).toDouble() ), 17 );
subTask->emitProgressChanged( 100 );
QCOMPARE( qRound( parent->progress() ), 50 );
QCOMPARE( spy.count(), 2 );
QCOMPARE( qRound( spy.last().at( 0 ).toDouble() ), 50 );
subTask2->emitTaskCompleted();
QCOMPARE( qRound( parent->progress() ), 83 );
QCOMPARE( spy.count(), 3 );
QCOMPARE( qRound( spy.last().at( 0 ).toDouble() ), 83 );
parent->emitProgressChanged( 100 );
QCOMPARE( qRound( parent->progress() ), 100 );
QCOMPARE( spy.count(), 4 );
QCOMPARE( qRound( spy.last().at( 0 ).toDouble() ), 100 );
delete parent;
// test canceling task with subtasks
parent = new TestTask();
subTask = new TestTask();
subsubTask = new TestTask();
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
parent->cancel();
QCOMPARE( subsubTask->status(), QgsTask::Terminated );
QCOMPARE( subTask->status(), QgsTask::Terminated );
QCOMPARE( parent->status(), QgsTask::Terminated );
delete parent;
// test that if a subtask terminates the parent task is cancelled
parent = new TestTask();
subTask = new TestTask();
subsubTask = new TestTask();
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
subsubTask->emitTaskStopped();
QCOMPARE( subsubTask->status(), QgsTask::Terminated );
QCOMPARE( subTask->status(), QgsTask::Terminated );
QCOMPARE( parent->status(), QgsTask::Terminated );
delete parent;
// test that a task is not marked complete until all subtasks are complete
parent = new TestTask();
subTask = new TestTask();
subsubTask = new TestTask();
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
parent->emitTaskCompleted();
QCOMPARE( subsubTask->status(), QgsTask::Queued );
QCOMPARE( subTask->status(), QgsTask::Queued );
//should still be running
QCOMPARE(( int )parent->status(), ( int )QgsTask::Running );
subTask->emitTaskCompleted();
QCOMPARE( parent->status(), QgsTask::Running );
QCOMPARE( subTask->status(), QgsTask::Running );
subsubTask->emitTaskCompleted();
QCOMPARE( subsubTask->status(), QgsTask::Complete );
QCOMPARE( subTask->status(), QgsTask::Complete );
QCOMPARE( parent->status(), QgsTask::Complete );
delete parent;
// another test
parent = new TestTask();
subTask = new TestTask();
subsubTask = new TestTask();
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
QCOMPARE( parent->status(), QgsTask::Queued );
QCOMPARE( subsubTask->status(), QgsTask::Queued );
QCOMPARE( subTask->status(), QgsTask::Queued );
subTask->emitTaskCompleted();
QCOMPARE( parent->status(), QgsTask::Queued );
QCOMPARE( subTask->status(), QgsTask::Running );
QCOMPARE( subsubTask->status(), QgsTask::Queued );
subsubTask->emitTaskCompleted();
QCOMPARE( subsubTask->status(), QgsTask::Complete );
QCOMPARE( subTask->status(), QgsTask::Complete );
QCOMPARE( parent->status(), QgsTask::Queued );
parent->emitTaskCompleted();
QCOMPARE( subsubTask->status(), QgsTask::Complete );
QCOMPARE( subTask->status(), QgsTask::Complete );
QCOMPARE( parent->status(), QgsTask::Complete );
delete parent;
}
void TestQgsTaskManager::taskId()
{
//test finding task IDs
@ -400,6 +539,15 @@ void TestQgsTaskManager::progressChanged()
manager.addTask( task );
manager.addTask( task2 );
while ( task->status() != QgsTask::Running ||
task2->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task->status(), QgsTask::Running );
QCOMPARE( task2->status(), QgsTask::Running );
QSignalSpy spy( &manager, &QgsTaskManager::progressChanged );
QSignalSpy spy2( &manager, &QgsTaskManager::finalTaskProgressChanged );
@ -408,7 +556,7 @@ void TestQgsTaskManager::progressChanged()
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( spy.last().at( 1 ).toDouble(), 50.0 );
//multiple running tasks, so progressChanged(double) should not be emitted
//multiple running tasks, so finalTaskProgressChanged(double) should not be emitted
QCOMPARE( spy2.count(), 0 );
task2->emitProgressChanged( 75.0 );
@ -423,24 +571,35 @@ void TestQgsTaskManager::progressChanged()
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task2->status(), QgsTask::Running );
task2->emitProgressChanged( 80.0 );
//single running task, so progressChanged(double) should be emitted
//single running task, so finalTaskProgressChanged(double) should be emitted
QCOMPARE( spy2.count(), 1 );
QCOMPARE( spy2.last().at( 0 ).toDouble(), 80.0 );
TestTask* task3 = new TestTask();
manager.addTask( task3 );
//multiple running tasks, so progressChanged(double) should not be emitted
while ( task3->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
//multiple running tasks, so finalTaskProgressChanged(double) should not be emitted
task2->emitProgressChanged( 81.0 );
QCOMPARE( spy2.count(), 1 );
QCOMPARE( task2->status(), QgsTask::Running );
QCOMPARE( task3->status(), QgsTask::Running );
task2->emitTaskStopped();
while ( manager.countActiveTasks() > 1 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
task3->emitProgressChanged( 30.0 );
//single running task, so progressChanged(double) should be emitted
//single running task, so finalTaskProgressChanged(double) should be emitted
QCOMPARE( spy2.count(), 2 );
QCOMPARE( spy2.last().at( 0 ).toDouble(), 30.0 );
}
@ -480,7 +639,11 @@ void TestQgsTaskManager::allTasksFinished()
TestTask* task2 = new TestTask();
manager.addTask( task );
manager.addTask( task2 );
while ( task2->status() != QgsTask::Running ) { }
while ( task->status() != QgsTask::Running || task2->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QSignalSpy spy( &manager, &QgsTaskManager::allTasksFinished );
@ -489,40 +652,57 @@ void TestQgsTaskManager::allTasksFinished()
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( spy.count(), 0 );
task2->emitTaskCompleted();
while ( manager.countActiveTasks() > 0 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( spy.count(), 1 );
TestTask* task3 = new TestTask();
TestTask* task4 = new TestTask();
manager.addTask( task3 );
while ( task3->status() != QgsTask::Running ) { }
while ( task3->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
manager.addTask( task4 );
while ( task4->status() != QgsTask::Running ) { }
while ( task4->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
task3->emitTaskStopped();
while ( manager.countActiveTasks() > 1 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( spy.count(), 1 );
TestTask* task5 = new TestTask();
manager.addTask( task5 );
while ( task5->status() != QgsTask::Running ) { }
while ( task5->status() != QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
task4->emitTaskStopped();
while ( manager.countActiveTasks() > 1 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( spy.count(), 1 );
task5->emitTaskStopped();
while ( manager.countActiveTasks() > 0 )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( spy.count(), 2 );
}
@ -544,13 +724,21 @@ void TestQgsTaskManager::activeTasks()
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toInt(), 2 );
task->emitTaskCompleted();
while ( task->status() == QgsTask::Running ) { }
while ( task->status() == QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( manager.activeTasks().toSet(), ( QList< QgsTask* >() << task2 ).toSet() );
QCOMPARE( manager.countActiveTasks(), 1 );
QCOMPARE( spy.count(), 3 );
QCOMPARE( spy.last().at( 0 ).toInt(), 1 );
task2->emitTaskCompleted();
while ( task2->status() == QgsTask::Running ) { }
while ( task2->status() == QgsTask::Running )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QVERIFY( manager.activeTasks().isEmpty() );
QCOMPARE( manager.countActiveTasks(), 0 );
QCOMPARE( spy.count(), 4 );
@ -569,7 +757,11 @@ void TestQgsTaskManager::holdTask()
task->unhold();
// wait for task to spin up
while ( task->status() == QgsTask::Queued ) {}
while ( task->status() == QgsTask::Queued )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task->status(), QgsTask::Running );
}
@ -585,8 +777,8 @@ void TestQgsTaskManager::dependancies()
TestTask* grandChildTask = new TestTask();
grandChildTask->hold();
long taskId = manager.addTask( task, QgsTaskList() << childTask );
long childTaskId = manager.addTask( childTask, QgsTaskList() << grandChildTask );
long taskId = manager.addTask( QgsTaskManager::TaskDefinition( task, QgsTaskList() << childTask ) );
long childTaskId = manager.addTask( QgsTaskManager::TaskDefinition( childTask, QgsTaskList() << grandChildTask ) );
long grandChildTaskId = manager.addTask( grandChildTask );
// check dependency resolution
@ -606,7 +798,7 @@ void TestQgsTaskManager::dependancies()
task = new TestTask();
childTask = new TestTask();
childTask->hold();
taskId = manager.addTask( task, QgsTaskList() << childTask );
taskId = manager.addTask( QgsTaskManager::TaskDefinition( task, QgsTaskList() << childTask ) );
childTaskId = manager.addTask( childTask );
QVERIFY( !manager.dependenciesSatisified( taskId ) );
QVERIFY( manager.dependenciesSatisified( childTaskId ) );
@ -616,16 +808,28 @@ void TestQgsTaskManager::dependancies()
childTask->unhold();
//wait for childTask to spin up
while ( !childTask->isActive() ) {}
while ( !childTask->isActive() )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( childTask->status(), QgsTask::Running );
QCOMPARE( task->status(), QgsTask::Queued );
childTask->emitTaskCompleted();
//wait for childTask to complete
while ( childTask->isActive() ) {}
while ( childTask->isActive() )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QVERIFY( manager.dependenciesSatisified( taskId ) );
QCOMPARE( childTask->status(), QgsTask::Complete );
//wait for task to spin up
while ( !task->isActive() ) {}
while ( !task->isActive() )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( task->status(), QgsTask::Running );
task->emitTaskCompleted();
@ -638,9 +842,9 @@ void TestQgsTaskManager::dependancies()
grandChildTask = new TestTask();
grandChildTask->hold();
taskId = manager.addTask( task, QgsTaskList() << childTask );
childTaskId = manager.addTask( childTask, QgsTaskList() << grandChildTask );
grandChildTaskId = manager.addTask( grandChildTask, QgsTaskList() << task );
taskId = manager.addTask( QgsTaskManager::TaskDefinition( task, QgsTaskList() << childTask ) );
childTaskId = manager.addTask( QgsTaskManager::TaskDefinition( childTask, QgsTaskList() << grandChildTask ) );
grandChildTaskId = manager.addTask( QgsTaskManager::TaskDefinition( grandChildTask, QgsTaskList() << task ) );
QVERIFY( manager.hasCircularDependencies( taskId ) );
QVERIFY( manager.hasCircularDependencies( childTaskId ) );
@ -682,6 +886,127 @@ void TestQgsTaskManager::layerDependencies()
QgsMapLayerRegistry::instance()->removeMapLayers( QList< QgsMapLayer* >() << layer2 );
}
void TestQgsTaskManager::managerWithSubTasks()
{
// parent with subtasks
TestTask* parent = new TestTask();
QPointer<TestTask> subTask( new TestTask() );
QPointer< TestTask> subsubTask( new TestTask() );
subTask->addSubTask( subsubTask );
parent->addSubTask( subTask );
QgsTaskManager manager;
QSignalSpy spy( &manager, &QgsTaskManager::taskAdded );
QSignalSpy spyProgress( &manager, &QgsTaskManager::progressChanged );
manager.addTask( parent );
// manager should only report 1 task added
QCOMPARE( manager.tasks().count(), 1 );
QVERIFY( manager.tasks().contains( parent ) );
QCOMPARE( manager.count(), 1 );
QCOMPARE( manager.countActiveTasks(), 1 );
QCOMPARE( manager.activeTasks().count(), 1 );
QVERIFY( manager.activeTasks().contains( parent ) );
QCOMPARE( spy.count(), 1 );
//manager should not directly listen to progress reports from subtasks
//(only parent tasks, which themselves include their subtask progress)
QCOMPARE( spyProgress.count(), 0 );
subTask->emitProgressChanged( 50 );
QCOMPARE( spyProgress.count(), 1 );
QCOMPARE( spyProgress.last().at( 0 ).toLongLong(), 0LL );
// subTask itself is 50% done, so with it's child task it's sitting at overall 25% done
// (one task 50%, one task not started)
// parent task has two tasks (itself + subTask), and subTask is 25% done.... so parent
// task is 12.5% done. yes-- these numbers are correct!
QCOMPARE( spyProgress.last().at( 1 ).toInt(), 13 );
subsubTask->emitProgressChanged( 100 );
QCOMPARE( spyProgress.count(), 2 );
QCOMPARE( spyProgress.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( spyProgress.last().at( 1 ).toInt(), 38 );
parent->emitProgressChanged( 50 );
QCOMPARE( spyProgress.count(), 3 );
QCOMPARE( spyProgress.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( spyProgress.last().at( 1 ).toInt(), 63 );
//manager should not emit statusChanged signals for subtasks
QSignalSpy statusSpy( &manager, &QgsTaskManager::statusChanged );
QCOMPARE( statusSpy.count(), 0 );
subsubTask->emitTaskCompleted();
while ( subsubTask->status() != QgsTask::Complete )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( statusSpy.count(), 1 );
QCOMPARE( statusSpy.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy.last().at( 1 ).toInt() ), QgsTask::Running );
subTask->emitTaskCompleted();
while ( subTask->status() != QgsTask::Complete )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( statusSpy.count(), 1 );
parent->emitTaskCompleted();
while ( parent->status() != QgsTask::Complete )
{
QCoreApplication::processEvents();
}
QCoreApplication::processEvents();
QCOMPARE( statusSpy.count(), 2 );
QCOMPARE( statusSpy.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy.last().at( 1 ).toInt() ), QgsTask::Complete );
subsubTask->emitTaskCompleted();
subTask->emitTaskCompleted();
parent->emitTaskCompleted();
//test dependencies
//test 1
QgsTaskManager manager2;
parent = new TestTask();
subTask = new TestTask();
QPointer<TestTask>subTask2( new TestTask() );
parent->addSubTask( subTask, QgsTaskList() << subTask2 );
parent->addSubTask( subTask2 );
manager2.addTask( parent );
long parentId = manager2.taskId( parent );
long subTaskId = manager2.taskId( subTask );
long subTask2Id = manager2.taskId( subTask2 );
QCOMPARE( manager2.dependencies( parentId ), QSet< long >() );
QCOMPARE( manager2.dependencies( subTaskId ), QSet< long >() << subTask2Id );
QCOMPARE( manager2.dependencies( subTask2Id ), QSet< long >() );
subTask2->emitTaskCompleted();
subTask->emitTaskCompleted();
parent->emitTaskCompleted();
//test 2
QgsTaskManager manager3;
parent = new TestTask();
subTask = new TestTask();
subTask2 = new TestTask();
parent->addSubTask( subTask, QgsTaskList() << subTask2 );
parent->addSubTask( subTask2, QgsTaskList(), QgsTask::ParentDependsOnSubTask );
manager3.addTask( parent );
parentId = manager3.taskId( parent );
subTaskId = manager3.taskId( subTask );
subTask2Id = manager3.taskId( subTask2 );
QCOMPARE( manager3.dependencies( parentId ), QSet< long >() << subTask2Id );
QCOMPARE( manager3.dependencies( subTaskId ), QSet< long >() << subTask2Id );
QCOMPARE( manager3.dependencies( subTask2Id ), QSet< long >() );
}
QTEST_MAIN( TestQgsTaskManager )
#include "testqgstaskmanager.moc"