QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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  , mTaskMutex( new QMutex( QMutex::Recursive ) )
393 {
394 
395 }
396 
398 {
399  //first tell all tasks to cancel
400  cancelAll();
401 
402  //then clean them up, including waiting for them to terminate
403  mTaskMutex->lock();
404  QMap< long, TaskInfo > tasks = mTasks;
405  mTasks.detach();
406  mTaskMutex->unlock();
407  QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
408  for ( ; it != tasks.constEnd(); ++it )
409  {
410  cleanupAndDeleteTask( it.value().task );
411  }
412 
413  delete mTaskMutex;
414 }
415 
416 long QgsTaskManager::addTask( QgsTask *task, int priority )
417 {
418  return addTaskPrivate( task, QgsTaskList(), false, priority );
419 }
420 
421 long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
422 {
423  return addTaskPrivate( definition.task,
424  definition.dependentTasks,
425  false,
426  priority );
427 }
428 
429 
430 long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
431 {
432  if ( !task )
433  return 0;
434 
435  if ( !mInitialized )
436  {
437  mInitialized = true;
438  // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
439  // cos that forces early creation of QgsProject
440  connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ),
441  this, &QgsTaskManager::layersWillBeRemoved );
442  }
443 
444  long taskId = mNextTaskId++;
445 
446  mTaskMutex->lock();
447  mTasks.insert( taskId, TaskInfo( task, priority ) );
448  if ( isSubTask )
449  {
450  mSubTasks << task;
451  }
452  else
453  {
454  mParentTasks << task;
455  }
456  if ( !task->dependentLayers().isEmpty() )
457  mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
458  mTaskMutex->unlock();
459 
460  connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
461  if ( !isSubTask )
462  {
463  connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
464  }
465 
466  // add all subtasks, must be done before dependency resolution
467  for ( const QgsTask::SubTask &subTask : qgis::as_const( task->mSubTasks ) )
468  {
469  switch ( subTask.dependency )
470  {
472  dependencies << subTask.task;
473  break;
474 
476  //nothing
477  break;
478  }
479  //recursively add sub tasks
480  addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
481  }
482 
483  if ( !dependencies.isEmpty() )
484  {
485  mTaskDependencies.insert( taskId, dependencies );
486  }
487 
488  if ( hasCircularDependencies( taskId ) )
489  {
490  task->cancel();
491  }
492 
493  if ( !isSubTask )
494  {
495  emit taskAdded( taskId );
496  processQueue();
497  }
498 
499  return taskId;
500 }
501 
502 QgsTask *QgsTaskManager::task( long id ) const
503 {
504  QMutexLocker ml( mTaskMutex );
505  QgsTask *t = nullptr;
506  if ( mTasks.contains( id ) )
507  t = mTasks.value( id ).task;
508  return t;
509 }
510 
511 QList<QgsTask *> QgsTaskManager::tasks() const
512 {
513  QMutexLocker ml( mTaskMutex );
514  return qgis::setToList( mParentTasks );
515 }
516 
518 {
519  QMutexLocker ml( mTaskMutex );
520  return mParentTasks.count();
521 }
522 
523 long QgsTaskManager::taskId( QgsTask *task ) const
524 {
525  if ( !task )
526  return -1;
527 
528  QMutexLocker ml( mTaskMutex );
529  QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
530  for ( ; it != mTasks.constEnd(); ++it )
531  {
532  if ( it.value().task == task )
533  {
534  return it.key();
535  }
536  }
537  return -1;
538 }
539 
541 {
542  mTaskMutex->lock();
543  QSet< QgsTask * > parents = mParentTasks;
544  parents.detach();
545  mTaskMutex->unlock();
546 
547  const auto constParents = parents;
548  for ( QgsTask *task : constParents )
549  {
550  task->cancel();
551  }
552 }
553 
554 bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
555 {
556  mTaskMutex->lock();
557  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
558  dependencies.detach();
559  mTaskMutex->unlock();
560 
561  if ( !dependencies.contains( taskId ) )
562  return true;
563 
564  const auto constValue = dependencies.value( taskId );
565  for ( QgsTask *task : constValue )
566  {
567  if ( task->status() != QgsTask::Complete )
568  return false;
569  }
570 
571  return true;
572 }
573 
574 QSet<long> QgsTaskManager::dependencies( long taskId ) const
575 {
576  QSet<long> results;
577  if ( resolveDependencies( taskId, taskId, results ) )
578  return results;
579  else
580  return QSet<long>();
581 }
582 
583 bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
584 {
585  mTaskMutex->lock();
586  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
587  dependencies.detach();
588  mTaskMutex->unlock();
589 
590  if ( !dependencies.contains( currentTaskId ) )
591  return true;
592 
593  const auto constValue = dependencies.value( currentTaskId );
594  for ( QgsTask *task : constValue )
595  {
596  long dependentTaskId = taskId( task );
597  if ( dependentTaskId >= 0 )
598  {
599  if ( dependentTaskId == firstTaskId )
600  // circular
601  return false;
602 
603  //add task as dependent
604  results.insert( dependentTaskId );
605  //plus all its other dependencies
606  QSet< long > newTaskDeps;
607  if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
608  return false;
609 
610  if ( newTaskDeps.contains( firstTaskId ) )
611  {
612  // circular
613  return false;
614  }
615 
616  results.unite( newTaskDeps );
617  }
618  }
619 
620  return true;
621 }
622 
623 bool QgsTaskManager::hasCircularDependencies( long taskId ) const
624 {
625  QSet< long > d;
626  return !resolveDependencies( taskId, taskId, d );
627 }
628 
629 QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
630 {
631  QMutexLocker ml( mTaskMutex );
632  return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
633 }
634 
635 QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
636 {
637  QMutexLocker ml( mTaskMutex );
638  QList< QgsTask * > tasks;
639  QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
640  for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
641  {
642  if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
643  {
644  QgsTask *layerTask = task( layerIt.key() );
645  if ( layerTask )
646  tasks << layerTask;
647  }
648  }
649  return tasks;
650 }
651 
652 QList<QgsTask *> QgsTaskManager::activeTasks() const
653 {
654  QMutexLocker ml( mTaskMutex );
655  QSet< QgsTask * > activeTasks = mActiveTasks;
656  activeTasks.intersect( mParentTasks );
657  return qgis::setToList( activeTasks );
658 }
659 
661 {
662  QMutexLocker ml( mTaskMutex );
663  QSet< QgsTask * > tasks = mActiveTasks;
664  return tasks.intersect( mParentTasks ).count();
665 }
666 
668 {
669  if ( task )
670  emit taskTriggered( task );
671 }
672 
673 void QgsTaskManager::taskProgressChanged( double progress )
674 {
675  QgsTask *task = qobject_cast< QgsTask * >( sender() );
676 
677  //find ID of task
678  long id = taskId( task );
679  if ( id < 0 )
680  return;
681 
682  emit progressChanged( id, progress );
683 
684  if ( countActiveTasks() == 1 )
685  {
686  emit finalTaskProgressChanged( progress );
687  }
688 }
689 
690 void QgsTaskManager::taskStatusChanged( int status )
691 {
692  QgsTask *task = qobject_cast< QgsTask * >( sender() );
693 
694  //find ID of task
695  long id = taskId( task );
696  if ( id < 0 )
697  return;
698 
699  mTaskMutex->lock();
700  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
701  mTaskMutex->unlock();
702  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
703  {
704  delete runnable;
705  mTasks[ id ].runnable = nullptr;
706  }
707 
708  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
709  {
710  bool result = status == QgsTask::Complete;
711  task->finished( result );
712  }
713 
714  if ( status == QgsTask::Terminated )
715  {
716  //recursively cancel dependent tasks
717  cancelDependentTasks( id );
718  }
719 
720  mTaskMutex->lock();
721  bool isParent = mParentTasks.contains( task );
722  mTaskMutex->unlock();
723  if ( isParent )
724  {
725  // don't emit status changed for subtasks
726  emit statusChanged( id, status );
727  }
728 
729  processQueue();
730 
731  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
732  {
733  cleanupAndDeleteTask( task );
734  }
735 
736 }
737 
738 void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
739 {
740  mTaskMutex->lock();
741  // scan through layers to be removed
742  QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
743  layerDependencies.detach();
744  mTaskMutex->unlock();
745 
746  const auto constLayers = layers;
747  for ( QgsMapLayer *layer : constLayers )
748  {
749  // scan through tasks with layer dependencies
750  for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
751  it != layerDependencies.constEnd(); ++it )
752  {
753  if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
754  {
755  //task not dependent on this layer
756  continue;
757  }
758 
759  QgsTask *dependentTask = task( it.key() );
760  if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
761  {
762  // incomplete task is dependent on this layer!
763  dependentTask->cancel();
764  }
765  }
766  }
767 }
768 
769 
770 bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
771 {
772  if ( !task )
773  return false;
774 
775  long id = taskId( task );
776  if ( id < 0 )
777  return false;
778 
779  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
780 
781  task->disconnect( this );
782 
783  mTaskMutex->lock();
784  if ( mTaskDependencies.contains( id ) )
785  mTaskDependencies.remove( id );
786  mTaskMutex->unlock();
787 
788  emit taskAboutToBeDeleted( id );
789 
790  mTaskMutex->lock();
791  bool isParent = mParentTasks.contains( task );
792  mParentTasks.remove( task );
793  mSubTasks.remove( task );
794  mTasks.remove( id );
795  mLayerDependencies.remove( id );
796 
798  {
799  if ( isParent )
800  {
801  // delete task when it's terminated
802  connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
803  connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
804  }
805  task->cancel();
806  }
807  else
808  {
809  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
810  {
811  delete runnable;
812  mTasks[ id ].runnable = nullptr;
813  }
814 
815  if ( isParent )
816  {
817  //task already finished, kill it
818  task->deleteLater();
819  }
820  }
821 
822  // at this stage (hopefully) dependent tasks have been canceled or queued
823  for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
824  {
825  if ( it.value().contains( task ) )
826  {
827  it.value().removeAll( task );
828  }
829  }
830  mTaskMutex->unlock();
831 
832  return true;
833 }
834 
835 void QgsTaskManager::processQueue()
836 {
837  int prevActiveCount = countActiveTasks();
838  mTaskMutex->lock();
839  mActiveTasks.clear();
840  for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
841  {
842  QgsTask *task = it.value().task;
843  if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
844  {
845  it.value().createRunnable();
846  QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
847  }
848 
849  if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
850  {
851  mActiveTasks << task;
852  }
853  }
854 
855  bool allFinished = mActiveTasks.isEmpty();
856  mTaskMutex->unlock();
857 
858  if ( allFinished )
859  {
860  emit allTasksFinished();
861  }
862 
863  int newActiveCount = countActiveTasks();
864  if ( prevActiveCount != newActiveCount )
865  {
866  emit countActiveTasksChanged( newActiveCount );
867  }
868 }
869 
870 void QgsTaskManager::cancelDependentTasks( long taskId )
871 {
872  QgsTask *canceledTask = task( taskId );
873 
874  //deep copy
875  mTaskMutex->lock();
876  QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
877  taskDependencies.detach();
878  mTaskMutex->unlock();
879 
880  QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
881  for ( ; it != taskDependencies.constEnd(); ++it )
882  {
883  if ( it.value().contains( canceledTask ) )
884  {
885  // found task with this dependency
886 
887  // cancel it - note that this will be recursive, so any tasks dependent
888  // on this one will also be canceled
889  QgsTask *dependentTask = task( it.key() );
890  if ( dependentTask )
891  dependentTask->cancel();
892  }
893  }
894 }
895 
896 QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
897  : task( task )
898  , added( 0 )
899  , priority( priority )
900 {}
901 
902 void QgsTaskManager::TaskInfo::createRunnable()
903 {
904  Q_ASSERT( !runnable );
905  runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
906 }
Base class for all map layer types.
Definition: qgsmaplayer.h:85
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
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:1776
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.