QGIS API Documentation  3.27.0-Master (0e23467727)
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_p.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  if ( !( task->flags() & QgsTask::Hidden ) )
500  emit taskAdded( taskId );
501 
502  processQueue();
503  }
504 
505  return taskId;
506 }
507 
508 QgsTask *QgsTaskManager::task( long id ) const
509 {
510  QMutexLocker ml( mTaskMutex );
511  QgsTask *t = nullptr;
512  if ( mTasks.contains( id ) )
513  t = mTasks.value( id ).task;
514  return t;
515 }
516 
517 QList<QgsTask *> QgsTaskManager::tasks() const
518 {
519  QMutexLocker ml( mTaskMutex );
520  return qgis::setToList( mParentTasks );
521 }
522 
524 {
525  QMutexLocker ml( mTaskMutex );
526  return mParentTasks.count();
527 }
528 
529 long QgsTaskManager::taskId( QgsTask *task ) const
530 {
531  if ( !task )
532  return -1;
533 
534  QMutexLocker ml( mTaskMutex );
535  QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
536  for ( ; it != mTasks.constEnd(); ++it )
537  {
538  if ( it.value().task == task )
539  {
540  return it.key();
541  }
542  }
543  return -1;
544 }
545 
547 {
548  mTaskMutex->lock();
549  QSet< QgsTask * > parents = mParentTasks;
550  parents.detach();
551  mTaskMutex->unlock();
552 
553  const auto constParents = parents;
554  for ( QgsTask *task : constParents )
555  {
556  task->cancel();
557  }
558 }
559 
560 bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
561 {
562  mTaskMutex->lock();
563  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
564  dependencies.detach();
565  mTaskMutex->unlock();
566 
567  if ( !dependencies.contains( taskId ) )
568  return true;
569 
570  const auto constValue = dependencies.value( taskId );
571  for ( QgsTask *task : constValue )
572  {
573  if ( task->status() != QgsTask::Complete )
574  return false;
575  }
576 
577  return true;
578 }
579 
580 QSet<long> QgsTaskManager::dependencies( long taskId ) const
581 {
582  QSet<long> results;
583  if ( resolveDependencies( taskId, taskId, results ) )
584  return results;
585  else
586  return QSet<long>();
587 }
588 
589 bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
590 {
591  mTaskMutex->lock();
592  QMap< long, QgsTaskList > dependencies = mTaskDependencies;
593  dependencies.detach();
594  mTaskMutex->unlock();
595 
596  if ( !dependencies.contains( currentTaskId ) )
597  return true;
598 
599  const auto constValue = dependencies.value( currentTaskId );
600  for ( QgsTask *task : constValue )
601  {
602  long dependentTaskId = taskId( task );
603  if ( dependentTaskId >= 0 )
604  {
605  if ( dependentTaskId == firstTaskId )
606  // circular
607  return false;
608 
609  //add task as dependent
610  results.insert( dependentTaskId );
611  //plus all its other dependencies
612  QSet< long > newTaskDeps;
613  if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
614  return false;
615 
616  if ( newTaskDeps.contains( firstTaskId ) )
617  {
618  // circular
619  return false;
620  }
621 
622  results.unite( newTaskDeps );
623  }
624  }
625 
626  return true;
627 }
628 
629 bool QgsTaskManager::hasCircularDependencies( long taskId ) const
630 {
631  QSet< long > d;
632  return !resolveDependencies( taskId, taskId, d );
633 }
634 
635 QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
636 {
637  QMutexLocker ml( mTaskMutex );
638  return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
639 }
640 
641 QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
642 {
643  QMutexLocker ml( mTaskMutex );
644  QList< QgsTask * > tasks;
645  QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
646  for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
647  {
648  if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
649  {
650  QgsTask *layerTask = task( layerIt.key() );
651  if ( layerTask )
652  tasks << layerTask;
653  }
654  }
655  return tasks;
656 }
657 
658 QList<QgsTask *> QgsTaskManager::activeTasks() const
659 {
660  QMutexLocker ml( mTaskMutex );
661  QSet< QgsTask * > activeTasks = mActiveTasks;
662  activeTasks.intersect( mParentTasks );
663  return qgis::setToList( activeTasks );
664 }
665 
666 int QgsTaskManager::countActiveTasks( bool includeHidden ) const
667 {
668  QMutexLocker ml( mTaskMutex );
669  QSet< QgsTask * > tasks = mActiveTasks;
670 
671  if ( !includeHidden )
672  {
673  QSet< QgsTask * > filteredTasks;
674  filteredTasks.reserve( tasks.size() );
675  for ( QgsTask *task : tasks )
676  {
677  if ( !( task->flags() & QgsTask::Hidden ) )
678  filteredTasks.insert( task );
679  }
680  tasks = filteredTasks;
681  }
682 
683  return tasks.intersect( mParentTasks ).count();
684 }
685 
687 {
688  if ( task )
689  emit taskTriggered( task );
690 }
691 
692 void QgsTaskManager::taskProgressChanged( double progress )
693 {
694  QgsTask *task = qobject_cast< QgsTask * >( sender() );
695  if ( task && task->flags() & QgsTask::Hidden )
696  return;
697 
698  //find ID of task
699  long id = taskId( task );
700  if ( id < 0 )
701  return;
702 
703  emit progressChanged( id, progress );
704 
705  if ( countActiveTasks( false ) == 1 )
706  {
707  emit finalTaskProgressChanged( progress );
708  }
709 }
710 
711 void QgsTaskManager::taskStatusChanged( int status )
712 {
713  QgsTask *task = qobject_cast< QgsTask * >( sender() );
714  const bool isHidden = task && task->flags() & QgsTask::Hidden;
715 
716  //find ID of task
717  long id = taskId( task );
718  if ( id < 0 )
719  return;
720 
721  mTaskMutex->lock();
722  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
723  mTaskMutex->unlock();
724  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
725  {
726  delete runnable;
727  mTasks[ id ].runnable = nullptr;
728  }
729 
730  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
731  {
732  bool result = status == QgsTask::Complete;
733  task->finished( result );
734  }
735 
736  if ( status == QgsTask::Terminated )
737  {
738  //recursively cancel dependent tasks
739  cancelDependentTasks( id );
740  }
741 
742  mTaskMutex->lock();
743  bool isParent = mParentTasks.contains( task );
744  mTaskMutex->unlock();
745  if ( isParent && !isHidden )
746  {
747  // don't emit status changed for subtasks
748  emit statusChanged( id, status );
749  }
750 
751  processQueue();
752 
753  if ( status == QgsTask::Terminated || status == QgsTask::Complete )
754  {
755  cleanupAndDeleteTask( task );
756  }
757 
758 }
759 
760 void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
761 {
762  mTaskMutex->lock();
763  // scan through layers to be removed
764  QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
765  layerDependencies.detach();
766  mTaskMutex->unlock();
767 
768  const auto constLayers = layers;
769  for ( QgsMapLayer *layer : constLayers )
770  {
771  // scan through tasks with layer dependencies
772  for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
773  it != layerDependencies.constEnd(); ++it )
774  {
775  if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
776  {
777  //task not dependent on this layer
778  continue;
779  }
780 
781  QgsTask *dependentTask = task( it.key() );
782  if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
783  {
784  // incomplete task is dependent on this layer!
785  dependentTask->cancel();
786  }
787  }
788  }
789 }
790 
791 
792 bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
793 {
794  if ( !task )
795  return false;
796 
797  long id = taskId( task );
798  if ( id < 0 )
799  return false;
800 
801  QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
802 
803  task->disconnect( this );
804 
805  mTaskMutex->lock();
806  if ( mTaskDependencies.contains( id ) )
807  mTaskDependencies.remove( id );
808  mTaskMutex->unlock();
809 
810  emit taskAboutToBeDeleted( id );
811 
812  mTaskMutex->lock();
813  bool isParent = mParentTasks.contains( task );
814  mParentTasks.remove( task );
815  mSubTasks.remove( task );
816  mTasks.remove( id );
817  mLayerDependencies.remove( id );
818 
820  {
821  if ( isParent )
822  {
823  // delete task when it's terminated
824  connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
825  connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
826  }
827  task->cancel();
828  }
829  else
830  {
831  if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
832  {
833  delete runnable;
834  mTasks[ id ].runnable = nullptr;
835  }
836 
837  if ( isParent )
838  {
839  //task already finished, kill it
840  task->deleteLater();
841  }
842  }
843 
844  // at this stage (hopefully) dependent tasks have been canceled or queued
845  for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
846  {
847  if ( it.value().contains( task ) )
848  {
849  it.value().removeAll( task );
850  }
851  }
852  mTaskMutex->unlock();
853 
854  return true;
855 }
856 
857 void QgsTaskManager::processQueue()
858 {
859  int prevActiveCount = countActiveTasks( false );
860  mTaskMutex->lock();
861  mActiveTasks.clear();
862  for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
863  {
864  QgsTask *task = it.value().task;
865  if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
866  {
867  it.value().createRunnable();
868  QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
869  }
870 
871  if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
872  {
873  mActiveTasks << task;
874  }
875  }
876 
877  bool allFinished = mActiveTasks.isEmpty();
878  mTaskMutex->unlock();
879 
880  if ( allFinished )
881  {
882  emit allTasksFinished();
883  }
884 
885  int newActiveCount = countActiveTasks( false );
886  if ( prevActiveCount != newActiveCount )
887  {
888  emit countActiveTasksChanged( newActiveCount );
889  }
890 }
891 
892 void QgsTaskManager::cancelDependentTasks( long taskId )
893 {
894  QgsTask *canceledTask = task( taskId );
895 
896  //deep copy
897  mTaskMutex->lock();
898  QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
899  taskDependencies.detach();
900  mTaskMutex->unlock();
901 
902  QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
903  for ( ; it != taskDependencies.constEnd(); ++it )
904  {
905  if ( it.value().contains( canceledTask ) )
906  {
907  // found task with this dependency
908 
909  // cancel it - note that this will be recursive, so any tasks dependent
910  // on this one will also be canceled
911  QgsTask *dependentTask = task( it.key() );
912  if ( dependentTask )
913  dependentTask->cancel();
914  }
915  }
916 }
917 
918 QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
919  : task( task )
920  , added( 0 )
921  , priority( priority )
922 {}
923 
924 void QgsTaskManager::TaskInfo::createRunnable()
925 {
926  Q_ASSERT( !runnable );
927  runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
928 }
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:104
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:479
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.
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.
int countActiveTasks(bool includeHidden=true) const
Returns the number of active (queued or running) tasks.
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.
Flags flags() const
Returns the flags associated with the task.
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.
@ Hidden
Hide task from GUI (since QGIS 3.26)
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:2153
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.