QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgstaskmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstaskmanager.cpp
3  ------------------
4  begin : April 2016
5  copyright : (C) 2016 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgstaskmanager.h"
19 #include "qgsproject.h"
20 #include "qgsmaplayerlistutils.h"
21 #include <mutex>
22 #include <QtConcurrentRun>
23 
24 
25 //
26 // QgsTask
27 //
28 
29 QgsTask::QgsTask( const QString &name, Flags flags )
30  : mFlags( flags )
31  , mDescription( name )
32  , mNotStartedMutex( 1 )
33 {
34  mNotStartedMutex.acquire();
35 }
36 
38 {
39  Q_ASSERT_X( mStatus != Running, "delete", QStringLiteral( "status was %1" ).arg( mStatus ).toLatin1() );
40  // even here we are not sure that task start method has ended
41  mNotFinishedMutex.lock();
42  const auto constMSubTasks = mSubTasks;
43  for ( const SubTask &subTask : constMSubTasks )
44  {
45  delete subTask.task;
46  }
47  mNotFinishedMutex.unlock();
48  mNotStartedMutex.release();
49 }
50 
51 void QgsTask::setDescription( const QString &description )
52 {
53  mDescription = description;
54 }
55 
56 qint64 QgsTask::elapsedTime() const
57 {
58  return mElapsedTime.elapsed();
59 }
60 
61 void QgsTask::start()
62 {
63  QMutexLocker locker( &mNotFinishedMutex );
64  mNotStartedMutex.release();
65  mStartCount++;
66  Q_ASSERT( mStartCount == 1 );
67 
68  if ( mStatus != Queued )
69  return;
70 
71  mStatus = Running;
72  mOverallStatus = Running;
73  mElapsedTime.start();
74 
75  emit statusChanged( Running );
76  emit begun();
77 
78  // force initial emission of progressChanged, but respect if task has had initial progress manually set
79  setProgress( mProgress );
80 
81  if ( run() )
82  {
83  completed();
84  }
85  else
86  {
87  terminated();
88  }
89 }
90 
92 {
93  if ( mOverallStatus == Complete || mOverallStatus == Terminated )
94  return;
95 
96  mShouldTerminateMutex.lock();
97  mShouldTerminate = true;
98  mShouldTerminateMutex.unlock();
99  if ( mStatus == Queued || mStatus == OnHold )
100  {
101  // immediately terminate unstarted jobs
102  terminated();
103  mNotStartedMutex.release();
104  }
105 
106  if ( mStatus == Terminated )
107  {
108  processSubTasksForTermination();
109  }
110 
111  const auto constMSubTasks = mSubTasks;
112  for ( const SubTask &subTask : constMSubTasks )
113  {
114  subTask.task->cancel();
115  }
116 }
117 
119 {
120  QMutexLocker locker( &mShouldTerminateMutex );
121  return mShouldTerminate;
122 }
123 
125 {
126  if ( mStatus == Queued )
127  {
128  mStatus = OnHold;
129  processSubTasksForHold();
130  }
131 
132  const auto constMSubTasks = mSubTasks;
133  for ( const SubTask &subTask : constMSubTasks )
134  {
135  subTask.task->hold();
136  }
137 }
138 
140 {
141  if ( mStatus == OnHold )
142  {
143  mStatus = Queued;
144  mOverallStatus = Queued;
145  emit statusChanged( Queued );
146  }
147 
148  const auto constMSubTasks = mSubTasks;
149  for ( const SubTask &subTask : constMSubTasks )
150  {
151  subTask.task->unhold();
152  }
153 }
154 
155 void QgsTask::addSubTask( QgsTask *subTask, const QgsTaskList &dependencies,
156  SubTaskDependency subTaskDependency )
157 {
158  mSubTasks << SubTask( subTask, dependencies, subTaskDependency );
159  connect( subTask, &QgsTask::progressChanged, this, [ = ] { setProgress( mProgress ); } );
160  connect( subTask, &QgsTask::statusChanged, this, &QgsTask::subTaskStatusChanged );
161 }
162 
163 QList<QgsMapLayer *> QgsTask::dependentLayers() const
164 {
165  return _qgis_listQPointerToRaw( mDependentLayers );
166 }
167 
168 bool QgsTask::waitForFinished( int timeout )
169 {
170  // We wait the task to be started
171  mNotStartedMutex.acquire();
172 
173  bool rv = true;
174  if ( mOverallStatus == Complete || mOverallStatus == Terminated )
175  {
176  rv = true;
177  }
178  else
179  {
180  if ( timeout == 0 )
181  timeout = std::numeric_limits< int >::max();
182  if ( mNotFinishedMutex.tryLock( timeout ) )
183  {
184  mNotFinishedMutex.unlock();
185  QCoreApplication::sendPostedEvents( this );
186  rv = true;
187  }
188  else
189  {
190  rv = false;
191  }
192  }
193  return rv;
194 }
195 
196 void QgsTask::setDependentLayers( const QList< QgsMapLayer * > &dependentLayers )
197 {
198  mDependentLayers = _qgis_listRawToQPointer( dependentLayers );
199 }
200 
201 void QgsTask::subTaskStatusChanged( int status )
202 {
203  QgsTask *subTask = qobject_cast< QgsTask * >( sender() );
204  if ( !subTask )
205  return;
206 
207  if ( status == Running && mStatus == Queued )
208  {
209  mOverallStatus = Running;
210  }
211  else if ( status == Complete && mStatus == Complete )
212  {
213  //check again if all subtasks are complete
214  processSubTasksForCompletion();
215  }
216  else if ( ( status == Complete || status == Terminated ) && mStatus == Terminated )
217  {
218  //check again if all subtasks are terminated
219  processSubTasksForTermination();
220  }
221  else if ( ( status == Complete || status == Terminated || status == OnHold ) && mStatus == OnHold )
222  {
223  processSubTasksForHold();
224  }
225  else if ( status == Terminated )
226  {
227  //uh oh...
228  cancel();
229  }
230 }
231 
232 void QgsTask::setProgress( double progress )
233 {
234  mProgress = progress;
235 
236  if ( !mSubTasks.isEmpty() )
237  {
238  // calculate total progress including subtasks
239 
240  double totalProgress = 0.0;
241  const auto constMSubTasks = mSubTasks;
242  for ( const SubTask &subTask : constMSubTasks )
243  {
244  if ( subTask.task->status() == QgsTask::Complete )
245  {
246  totalProgress += 100.0;
247  }
248  else
249  {
250  totalProgress += subTask.task->progress();
251  }
252  }
253  progress = ( progress + totalProgress ) / ( mSubTasks.count() + 1 );
254  }
255 
256  // avoid flooding with too many events
257  double prevProgress = mTotalProgress;
258  mTotalProgress = progress;
259 
260  // avoid spamming with too many progressChanged reports
261  if ( static_cast< int >( prevProgress * 10 ) != static_cast< int >( mTotalProgress * 10 ) )
262  emit progressChanged( progress );
263 }
264 
265 void QgsTask::completed()
266 {
267  mStatus = Complete;
268  QMetaObject::invokeMethod( this, "processSubTasksForCompletion" );
269 }
270 
271 void QgsTask::processSubTasksForCompletion()
272 {
273  bool subTasksCompleted = true;
274  const auto constMSubTasks = mSubTasks;
275  for ( const SubTask &subTask : constMSubTasks )
276  {
277  if ( subTask.task->status() != Complete )
278  {
279  subTasksCompleted = false;
280  break;
281  }
282  }
283 
284  if ( mStatus == Complete && subTasksCompleted )
285  {
286  mOverallStatus = Complete;
287 
288  setProgress( 100.0 );
289  emit statusChanged( Complete );
290  emit taskCompleted();
291  }
292  else if ( mStatus == Complete )
293  {
294  // defer completion until all subtasks are complete
295  mOverallStatus = Running;
296  }
297 }
298 
299 void QgsTask::processSubTasksForTermination()
300 {
301  bool subTasksTerminated = true;
302  const auto constMSubTasks = mSubTasks;
303  for ( const SubTask &subTask : constMSubTasks )
304  {
305  if ( subTask.task->status() != Terminated && subTask.task->status() != Complete )
306  {
307  subTasksTerminated = false;
308  break;
309  }
310  }
311 
312  if ( mStatus == Terminated && subTasksTerminated && mOverallStatus != Terminated )
313  {
314  mOverallStatus = Terminated;
315 
316  emit statusChanged( Terminated );
317  emit taskTerminated();
318  }
319  else if ( mStatus == Terminated && !subTasksTerminated )
320  {
321  // defer termination until all subtasks are terminated (or complete)
322  mOverallStatus = Running;
323  }
324 }
325 
326 void QgsTask::processSubTasksForHold()
327 {
328  bool subTasksRunning = false;
329  const auto constMSubTasks = mSubTasks;
330  for ( const SubTask &subTask : constMSubTasks )
331  {
332  if ( subTask.task->status() == Running )
333  {
334  subTasksRunning = true;
335  break;
336  }
337  }
338 
339  if ( mStatus == OnHold && !subTasksRunning && mOverallStatus != OnHold )
340  {
341  mOverallStatus = OnHold;
342  emit statusChanged( OnHold );
343  }
344  else if ( mStatus == OnHold && subTasksRunning )
345  {
346  // defer hold until all subtasks finish running
347  mOverallStatus = Running;
348  }
349 }
350 
351 void QgsTask::terminated()
352 {
353  mStatus = Terminated;
354  QMetaObject::invokeMethod( this, "processSubTasksForTermination" );
355 }
356 
357 
359 
360 class QgsTaskRunnableWrapper : public QRunnable
361 {
362  public:
363 
364  explicit QgsTaskRunnableWrapper( QgsTask *task )
365  : mTask( task )
366  {
367  setAutoDelete( true );
368  }
369 
370  void run() override
371  {
372  Q_ASSERT( mTask );
373  mTask->start();
374  }
375 
376  private:
377 
378  QgsTask *mTask = nullptr;
379 
380 };
381 
383 
384 
385 
386 //
387 // QgsTaskManager
388 //
389 
391  : QObject( parent )
392 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
393  , mTaskMutex( new QMutex( QMutex::Recursive ) )
394 #else
395  , mTaskMutex( new QRecursiveMutex() )
396 #endif
397 {
398 
399 }
400 
402 {
403  //first tell all tasks to cancel
404  cancelAll();
405 
406  //then clean them up, including waiting for them to terminate
407  mTaskMutex->lock();
408  QMap< long, TaskInfo > tasks = mTasks;
409  mTasks.detach();
410  mTaskMutex->unlock();
411  QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
412  for ( ; it != tasks.constEnd(); ++it )
413  {
414  cleanupAndDeleteTask( it.value().task );
415  }
416 
417  delete mTaskMutex;
418 }
419 
420 long QgsTaskManager::addTask( QgsTask *task, int priority )
421 {
422  return addTaskPrivate( task, QgsTaskList(), false, priority );
423 }
424 
425 long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
426 {
427  return addTaskPrivate( definition.task,
428  definition.dependentTasks,
429  false,
430  priority );
431 }
432 
433 
434 long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
435 {
436  if ( !task )
437  return 0;
438 
439  if ( !mInitialized )
440  {
441  mInitialized = true;
442  // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
443  // cos that forces early creation of QgsProject
444  connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ),
445  this, &QgsTaskManager::layersWillBeRemoved );
446  }
447 
448  long taskId = mNextTaskId++;
449 
450  mTaskMutex->lock();
451  mTasks.insert( taskId, TaskInfo( task, priority ) );
452  if ( isSubTask )
453  {
454  mSubTasks << task;
455  }
456  else
457  {
458  mParentTasks << task;
459  }
460  if ( !task->dependentLayers().isEmpty() )
461  mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
462  mTaskMutex->unlock();
463 
464  connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
465  if ( !isSubTask )
466  {
467  connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
468  }
469 
470  // add all subtasks, must be done before dependency resolution
471  for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
472  {
473  switch ( subTask.dependency )
474  {
476  dependencies << subTask.task;
477  break;
478 
480  //nothing
481  break;
482  }
483  //recursively add sub tasks
484  addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
485  }
486 
487  if ( !dependencies.isEmpty() )
488  {
489  mTaskDependencies.insert( taskId, dependencies );
490  }
491 
492  if ( hasCircularDependencies( taskId ) )
493  {
494  task->cancel();
495  }
496 
497  if ( !isSubTask )
498  {
499  emit taskAdded( taskId );
500  processQueue();
501  }
502 
503  return taskId;
504 }
505 
506 QgsTask *QgsTaskManager::task( long id ) const
507 {
508  QMutexLocker ml( mTaskMutex );
509  QgsTask *t = nullptr;
510  if ( mTasks.contains( id ) )
511  t = mTasks.value( id ).task;
512  return t;
513 }
514 
515 QList<QgsTask *> QgsTaskManager::tasks() const
516 {
517  QMutexLocker ml( mTaskMutex );
518  return qgis::setToList( mParentTasks );
519 }
520 
522 {
523  QMutexLocker ml( mTaskMutex );
524  return mParentTasks.count();
525 }
526 
527 long QgsTaskManager::taskId( QgsTask *task ) const
528 {
529  if ( !task )
530  return -1;
531 
532  QMutexLocker ml( mTaskMutex );
533  QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
534  for ( ; it != mTasks.constEnd(); ++it )
535  {
536  if ( it.value().task == task )
537  {
538  return it.key();
539  }
540  }
541  return -1;
542 }
543 
545 {
546  mTaskMutex->lock();
547  QSet< QgsTask * > parents = mParentTasks;
548  parents.detach();
549  mTaskMutex->unlock();
550 
551  const auto constParents = parents;
552  for ( QgsTask *task : constParents )
553  {
554  task->cancel();
555  }
556 }
557 
558 bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
559 {
560  mTaskMutex->lock();
561  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
562  dependencies.detach();
563  mTaskMutex->unlock();
564 
565  if ( !dependencies.contains( taskId ) )
566  return true;
567 
568  const auto constValue = dependencies.value( taskId );
569  for ( QgsTask *task : constValue )
570  {
571  if ( task->status() != QgsTask::Complete )
572  return false;
573  }
574 
575  return true;
576 }
577 
578 QSet<long> QgsTaskManager::dependencies( long taskId ) const
579 {
580  QSet<long> results;
581  if ( resolveDependencies( taskId, taskId, results ) )
582  return results;
583  else
584  return QSet<long>();
585 }
586 
587 bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
588 {
589  mTaskMutex->lock();
590  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
591  dependencies.detach();
592  mTaskMutex->unlock();
593 
594  if ( !dependencies.contains( currentTaskId ) )
595  return true;
596 
597  const auto constValue = dependencies.value( currentTaskId );
598  for ( QgsTask *task : constValue )
599  {
600  long dependentTaskId = taskId( task );
601  if ( dependentTaskId >= 0 )
602  {
603  if ( dependentTaskId == firstTaskId )
604  // circular
605  return false;
606 
607  //add task as dependent
608  results.insert( dependentTaskId );
609  //plus all its other dependencies
610  QSet< long > newTaskDeps;
611  if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
612  return false;
613 
614  if ( newTaskDeps.contains( firstTaskId ) )
615  {
616  // circular
617  return false;
618  }
619 
620  results.unite( newTaskDeps );
621  }
622  }
623 
624  return true;
625 }
626 
627 bool QgsTaskManager::hasCircularDependencies( long taskId ) const
628 {
629  QSet< long > d;
630  return !resolveDependencies( taskId, taskId, d );
631 }
632 
633 QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
634 {
635  QMutexLocker ml( mTaskMutex );
636  return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
637 }
638 
639 QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
640 {
641  QMutexLocker ml( mTaskMutex );
642  QList< QgsTask * > tasks;
643  QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
644  for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
645  {
646  if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
647  {
648  QgsTask *layerTask = task( layerIt.key() );
649  if ( layerTask )
650  tasks << layerTask;
651  }
652  }
653  return tasks;
654 }
655 
656 QList<QgsTask *> QgsTaskManager::activeTasks() const
657 {
658  QMutexLocker ml( mTaskMutex );
659  QSet< QgsTask * > activeTasks = mActiveTasks;
660  activeTasks.intersect( mParentTasks );
661  return qgis::setToList( activeTasks );
662 }
663 
665 {
666  QMutexLocker ml( mTaskMutex );
667  QSet< QgsTask * > tasks = mActiveTasks;
668  return tasks.intersect( mParentTasks ).count();
669 }
670 
672 {
673  if ( task )
674  emit taskTriggered( task );
675 }
676 
677 void QgsTaskManager::taskProgressChanged( double progress )
678 {
679  QgsTask *task = qobject_cast< QgsTask * >( sender() );
680 
681  //find ID of task
682  long id = taskId( task );
683  if ( id < 0 )
684  return;
685 
686  emit progressChanged( id, progress );
687 
688  if ( countActiveTasks() == 1 )
689  {
690  emit finalTaskProgressChanged( progress );
691  }
692 }
693 
694 void QgsTaskManager::taskStatusChanged( int status )
695 {
696  QgsTask *task = qobject_cast< QgsTask * >( sender() );
697 
698  //find ID of task
699  long id = taskId( task );
700  if ( id < 0 )
701  return;
702 
703  mTaskMutex->lock();
704  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
705  mTaskMutex->unlock();
706  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
707  {
708  delete runnable;
709  mTasks[ id ].runnable = nullptr;
710  }
711 
712  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
713  {
714  bool result = status == QgsTask::Complete;
715  task->finished( result );
716  }
717 
718  if ( status == QgsTask::Terminated )
719  {
720  //recursively cancel dependent tasks
721  cancelDependentTasks( id );
722  }
723 
724  mTaskMutex->lock();
725  bool isParent = mParentTasks.contains( task );
726  mTaskMutex->unlock();
727  if ( isParent )
728  {
729  // don't emit status changed for subtasks
730  emit statusChanged( id, status );
731  }
732 
733  processQueue();
734 
735  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
736  {
737  cleanupAndDeleteTask( task );
738  }
739 
740 }
741 
742 void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
743 {
744  mTaskMutex->lock();
745  // scan through layers to be removed
746  QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
747  layerDependencies.detach();
748  mTaskMutex->unlock();
749 
750  const auto constLayers = layers;
751  for ( QgsMapLayer *layer : constLayers )
752  {
753  // scan through tasks with layer dependencies
754  for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
755  it != layerDependencies.constEnd(); ++it )
756  {
757  if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
758  {
759  //task not dependent on this layer
760  continue;
761  }
762 
763  QgsTask *dependentTask = task( it.key() );
764  if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
765  {
766  // incomplete task is dependent on this layer!
767  dependentTask->cancel();
768  }
769  }
770  }
771 }
772 
773 
774 bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
775 {
776  if ( !task )
777  return false;
778 
779  long id = taskId( task );
780  if ( id < 0 )
781  return false;
782 
783  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
784 
785  task->disconnect( this );
786 
787  mTaskMutex->lock();
788  if ( mTaskDependencies.contains( id ) )
789  mTaskDependencies.remove( id );
790  mTaskMutex->unlock();
791 
792  emit taskAboutToBeDeleted( id );
793 
794  mTaskMutex->lock();
795  bool isParent = mParentTasks.contains( task );
796  mParentTasks.remove( task );
797  mSubTasks.remove( task );
798  mTasks.remove( id );
799  mLayerDependencies.remove( id );
800 
802  {
803  if ( isParent )
804  {
805  // delete task when it's terminated
806  connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
807  connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
808  }
809  task->cancel();
810  }
811  else
812  {
813  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
814  {
815  delete runnable;
816  mTasks[ id ].runnable = nullptr;
817  }
818 
819  if ( isParent )
820  {
821  //task already finished, kill it
822  task->deleteLater();
823  }
824  }
825 
826  // at this stage (hopefully) dependent tasks have been canceled or queued
827  for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
828  {
829  if ( it.value().contains( task ) )
830  {
831  it.value().removeAll( task );
832  }
833  }
834  mTaskMutex->unlock();
835 
836  return true;
837 }
838 
839 void QgsTaskManager::processQueue()
840 {
841  int prevActiveCount = countActiveTasks();
842  mTaskMutex->lock();
843  mActiveTasks.clear();
844  for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
845  {
846  QgsTask *task = it.value().task;
847  if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
848  {
849  it.value().createRunnable();
850  QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
851  }
852 
853  if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
854  {
855  mActiveTasks << task;
856  }
857  }
858 
859  bool allFinished = mActiveTasks.isEmpty();
860  mTaskMutex->unlock();
861 
862  if ( allFinished )
863  {
864  emit allTasksFinished();
865  }
866 
867  int newActiveCount = countActiveTasks();
868  if ( prevActiveCount != newActiveCount )
869  {
870  emit countActiveTasksChanged( newActiveCount );
871  }
872 }
873 
874 void QgsTaskManager::cancelDependentTasks( long taskId )
875 {
876  QgsTask *canceledTask = task( taskId );
877 
878  //deep copy
879  mTaskMutex->lock();
880  QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
881  taskDependencies.detach();
882  mTaskMutex->unlock();
883 
884  QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
885  for ( ; it != taskDependencies.constEnd(); ++it )
886  {
887  if ( it.value().contains( canceledTask ) )
888  {
889  // found task with this dependency
890 
891  // cancel it - note that this will be recursive, so any tasks dependent
892  // on this one will also be canceled
893  QgsTask *dependentTask = task( it.key() );
894  if ( dependentTask )
895  dependentTask->cancel();
896  }
897  }
898 }
899 
900 QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
901  : task( task )
902  , added( 0 )
903  , priority( priority )
904 {}
905 
906 void QgsTaskManager::TaskInfo::createRunnable()
907 {
908  Q_ASSERT( !runnable );
909  runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
910 }
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
QList< QgsTask * > tasks() const
Returns all tasks tracked by the manager.
void finalTaskProgressChanged(double progress)
Will be emitted when only a single task remains to complete and that task has reported a progress cha...
void statusChanged(long taskId, int status)
Will be emitted when a task reports a status change.
void taskAdded(long taskId)
Emitted when a new task has been added to the manager.
int countActiveTasks() const
Returns the number of active (queued or running) tasks.
QList< QgsTask * > activeTasks() const
Returns a list of the active (queued or running) tasks.
QgsTaskManager(QObject *parent=nullptr)
Constructor for QgsTaskManager.
void taskAboutToBeDeleted(long taskId)
Emitted when a task is about to be deleted.
long taskId(QgsTask *task) const
Returns the unique task ID corresponding to a task managed by the class.
int count() const
Returns the number of tasks tracked by the manager.
QList< QgsTask * > tasksDependentOnLayer(QgsMapLayer *layer) const
Returns a list of tasks which depend on a layer.
void allTasksFinished()
Emitted when all tasks are complete.
~QgsTaskManager() override
bool dependenciesSatisfied(long taskId) const
Returns true if all dependencies for the specified task are satisfied.
void cancelAll()
Instructs all tasks tracked by the manager to terminate.
QSet< long > dependencies(long taskId) const
Returns the set of task IDs on which a task is dependent.
QgsTask * task(long id) const
Returns the task with matching ID.
void progressChanged(long taskId, double progress)
Will be emitted when a task reports a progress change.
void triggerTask(QgsTask *task)
Triggers a task, e.g.
void countActiveTasksChanged(int count)
Emitted when the number of active tasks changes.
QList< QgsMapLayer * > dependentLayers(long taskId) const
Returns a list of layers on which as task is dependent.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskTriggered(QgsTask *task)
Emitted when a task is triggered.
Abstract base class for long running background tasks.
TaskStatus status() const
Returns the current task status.
void taskCompleted()
Will be emitted by task to indicate its successful completion.
double progress() const
Returns the task's progress (between 0.0 and 100.0)
virtual void finished(bool result)
If the task is managed by a QgsTaskManager, this will be called after the task has finished (whether ...
~QgsTask() override
virtual bool run()=0
Performs the task's operation.
void progressChanged(double progress)
Will be emitted by task when its progress changes.
QList< QgsMapLayer * > dependentLayers() const
Returns the list of layers on which the task depends.
void begun()
Will be emitted by task to indicate its commencement.
virtual void cancel()
Notifies the task that it should terminate.
QgsTask(const QString &description=QString(), QgsTask::Flags flags=AllFlags)
Constructor for QgsTask.
void taskTerminated()
Will be emitted by task if it has terminated for any reason other then completion (e....
void statusChanged(int status)
Will be emitted by task when its status changes.
qint64 elapsedTime() const
Returns the elapsed time since the task commenced, in milliseconds.
void unhold()
Releases the task from being held.
void setDependentLayers(const QList< QgsMapLayer * > &dependentLayers)
Sets a list of layers on which the task depends.
@ Terminated
Task was terminated or errored.
@ Queued
Task is queued and has not begun.
@ OnHold
Task is queued but on hold and will not be started.
@ Running
Task is currently running.
@ Complete
Task successfully completed.
void hold()
Places the task on hold.
QString description() const
Returns the task's description.
void addSubTask(QgsTask *subTask, const QgsTaskList &dependencies=QgsTaskList(), SubTaskDependency subTaskDependency=SubTaskIndependent)
Adds a subtask to this task.
void setDescription(const QString &description)
Sets the task's description.
SubTaskDependency
Controls how subtasks relate to their parent task.
@ 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.
bool isCanceled() const
Will return true if task should terminate ASAP.
void setProgress(double progress)
Sets the task's current progress.
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
QList< QgsWeakMapLayerPointer > QgsWeakMapLayerPointerList
A list of weak pointers to QgsMapLayers.
Definition: qgsmaplayer.h:2136
QList< QgsTask * > QgsTaskList
List of QgsTask objects.
Definition of a task for inclusion in the manager.
QgsTaskList dependentTasks
List of dependent tasks which must be completed before task can run.