Skip to content
Permalink
Browse files
Make QgsTaskManager handle threading of tasks
  • Loading branch information
nyalldawson committed Dec 5, 2016
1 parent ebae15f commit 6021d7806e58cc19c2b904e3c62ec4303bbd2a2f
Showing with 175 additions and 40 deletions.
  1. +27 −7 python/core/qgstaskmanager.sip
  2. +62 −14 src/core/qgstaskmanager.cpp
  3. +43 −13 src/core/qgstaskmanager.h
  4. +3 −0 src/gui/qgstaskmanagerwidget.cpp
  5. +40 −6 tests/src/core/testqgstaskmanager.cpp
@@ -14,22 +14,23 @@ class QgsTask : QObject
//! Status of tasks
enum TaskStatus
{
Queued, /*!< Task is queued and has not begun */
Running, /*!< Task is currently running */
Complete, /*!< Task successfully completed */
Terminated, /*!< Task was terminated or errored */
// Paused,
// Queued,
};

/** Constructor for QgsTask.
* @param description text description of task
*/
QgsTask( const QString& description = QString() );

//! Will be called when task has been terminated, either through
//! user interaction or other reason (eg application exit)
//! @note derived classes must ensure they call the base method
virtual void terminate();
//! Starts the task.
void start();

//! Notifies the task that it should terminate.
//! @see isCancelled()
void terminate();

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

//! Will be emitted by task to indicate its commencement.
//! @note derived classes should not emit this signal directly, it will automatically
//! be emitted when the task begins
void begun();

//! Will be emitted by task to indicate its completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! completed()
@@ -69,7 +75,7 @@ class QgsTask : QObject
//! stopped()//!
void taskStopped();

protected:
public slots:

//! Sets the task's current progress. Should be called whenever the
//! task wants to update it's progress. Calling will automatically emit the progressChanged
@@ -85,6 +91,17 @@ class QgsTask : QObject
//! reason other than successful completion.
//! Calling will automatically emit the statusChanged and taskStopped signals.
void stopped();

protected:

//! Derived tasks must implement a run() method. This method will be called when the
//! task commences (ie via calling start() ).
virtual void run() = 0;

//! Will return true if task should terminate ASAP. Derived classes run() methods
//! should periodically check this and terminate in a safe manner.
bool isCancelled() const;

};

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

//! Instructs all tasks tracked by the manager to terminate.
void terminateAll();

signals:

//! Will be emitted when a task reports a progress change
@@ -16,7 +16,7 @@
***************************************************************************/

#include "qgstaskmanager.h"
#include <QMutex>
#include <QtConcurrentRun>


//
@@ -26,10 +26,24 @@
QgsTask::QgsTask( const QString &name )
: QObject()
, mDescription( name )
, mStatus( Running )
, mStatus( Queued )
, mProgress( 0.0 )
, mShouldTerminate( false )
{}

void QgsTask::start()
{
mStatus = Running;
emit statusChanged( Running );
emit begun();
run();
}

void QgsTask::terminate()
{
mShouldTerminate = true;
}

void QgsTask::setProgress( double progress )
{
mProgress = progress;
@@ -74,28 +88,33 @@ QgsTaskManager::QgsTaskManager( QObject* parent )

QgsTaskManager::~QgsTaskManager()
{
QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
//first tell all tasks to cancel
terminateAll();

//then clean them up, including waiting for them to terminate
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
{
cleanupAndDeleteTask( it.value() );
cleanupAndDeleteTask( it.value().task );
}
}

long QgsTaskManager::addTask( QgsTask* task )
{
static QMutex sAddMutex( QMutex::Recursive );
QMutexLocker locker( &sAddMutex );

mTasks.insert( mNextTaskId, task );

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

mTasks[ mNextTaskId ].future = QtConcurrent::run( task, &QgsTask::start );

emit taskAdded( mNextTaskId );
return mNextTaskId++;
}

bool QgsTaskManager::deleteTask( long id )
{
QgsTask* task = mTasks.value( id );
QgsTask* task = mTasks.value( id ).task;
return deleteTask( task );
}

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

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

QgsTask*QgsTaskManager::task( long id ) const
{
return mTasks.value( id );
return mTasks.value( id ).task;
}

QList<QgsTask*> QgsTaskManager::tasks() const
{
return mTasks.values();
QList< QgsTask* > list;
for ( QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin(); it != mTasks.constEnd(); ++it )
{
list << it.value().task;
}
return list;
}

long QgsTaskManager::taskId( QgsTask *task ) const
{
if ( !task )
return -1;

QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
{
if ( it.value() == task )
if ( it.value().task == task )
return it.key();
}
return -1;
}

void QgsTaskManager::terminateAll()
{
QMap< long, TaskInfo >::iterator it = mTasks.begin();
for ( ; it != mTasks.end(); ++it )
{
QgsTask* task = it.value().task;
if ( task->isActive() )
{
task->terminate();
}
}
}

void QgsTaskManager::taskProgressChanged( double progress )
{
QgsTask* task = qobject_cast< QgsTask* >( sender() );
@@ -174,6 +211,17 @@ bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
if ( task->isActive() )
task->terminate();

// wait for task to terminate
QMap< long, TaskInfo >::iterator it = mTasks.begin();
for ( ; it != mTasks.end(); ++it )
{
if ( it.value().task == task )
{
it.value().future.waitForFinished();
break;
}
}

emit taskAboutToBeDeleted( taskId( task ) );

task->deleteLater();
@@ -21,10 +21,12 @@
#include <QObject>
#include <QMap>
#include <QAbstractItemModel>
#include <QFuture>

/** \ingroup core
* \class QgsTask
* \brief Interface class for long running background tasks which will be handled by a QgsTaskManager
* \brief Interface class for long running background tasks. Tasks can be controlled directly,
* or added to a QgsTaskManager for automatic management.
* \note Added in version 2.16
*/
class CORE_EXPORT QgsTask : public QObject
@@ -36,25 +38,23 @@ class CORE_EXPORT QgsTask : public QObject
//! Status of tasks
enum TaskStatus
{
Queued, /*!< Task is queued and has not begun */
Running, /*!< Task is currently running */
Complete, /*!< Task successfully completed */
Terminated, /*!< Task was terminated or errored */
// Paused,
// Queued,
};

/** Constructor for QgsTask.
* @param description text description of task
*/
QgsTask( const QString& description = QString() );

//! Will be called when task has been terminated, either through
//! user interaction or other reason (eg application exit)
//! @note derived classes must ensure they call the base method
virtual void terminate()
{
stopped();
}
//! Starts the task.
void start();

//! Notifies the task that it should terminate.
//! @see isCancelled()
void terminate();

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

//! Will be emitted by task to indicate its commencement.
//! @note derived classes should not emit this signal directly, it will automatically
//! be emitted when the task begins
void begun();

//! Will be emitted by task to indicate its completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! completed()
@@ -94,7 +99,7 @@ class CORE_EXPORT QgsTask : public QObject
//! stopped()//!
void taskStopped();

protected:
public slots:

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

protected:

//! Derived tasks must implement a run() method. This method will be called when the
//! task commences (ie via calling start() ).
virtual void run() = 0;

//! Will return true if task should terminate ASAP. Derived classes run() methods
//! should periodically check this and terminate in a safe manner.
bool isCancelled() const { return mShouldTerminate; }

private:

QString mDescription;
TaskStatus mStatus;
double mProgress;
bool mShouldTerminate;

};

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

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

//! Instructs all tasks tracked by the manager to terminate.
void terminateAll();

signals:

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

static QgsTaskManager *sInstance;
QMap< long, QgsTask* > mTasks;

struct TaskInfo
{
TaskInfo( QgsTask* task = nullptr )
: task( task )
{}
QgsTask* task;
QFuture< void > future;
};

QMap< long, TaskInfo > mTasks;

//! Tracks the next unique task ID
long mNextTaskId;
@@ -321,6 +321,9 @@ void QgsTaskStatusDelegate::paint( QPainter *painter, const QStyleOptionViewItem
QIcon icon;
switch ( static_cast< QgsTask::TaskStatus >( index.data().toInt() ) )
{
case QgsTask::Queued:
icon = QgsApplication::getThemeIcon( "/mIconFieldTime.svg" );
break;
case QgsTask::Running:
icon = QgsApplication::getThemeIcon( "/mActionRefresh.png" );
break;
Loading

0 comments on commit 6021d78

Please sign in to comment.