mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
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:
parent
3999a3710f
commit
32a9e1e9d6
@ -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
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user