QGIS API Documentation 3.99.0-Master (d31f8633d82)
Loading...
Searching...
No Matches
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
20#include <mutex>
21
23#include "qgsproject.h"
24
25#include <QStack>
26#include <QString>
27
28#include "moc_qgstaskmanager.cpp"
29
30using namespace Qt::StringLiterals;
31
32//
33// QgsTask
34//
35
36QgsTask::QgsTask( const QString &name, Flags flags )
37 : mFlags( flags )
38 , mDescription( name )
39 , mNotStartedMutex( 1 )
40{
41 mNotStartedMutex.acquire();
42}
43
45{
46 Q_ASSERT_X( mStatus != Running, "delete", u"status was %1"_s.arg( mStatus ).toLatin1() );
47 // even here we are not sure that task start method has ended
48 mNotFinishedMutex.lock();
49 const auto constMSubTasks = mSubTasks;
50 for ( const SubTask &subTask : constMSubTasks )
51 {
52 delete subTask.task;
53 }
54 mNotFinishedMutex.unlock();
55 mNotStartedMutex.release();
56}
57
59{
60 mDescription = description;
61}
62
64{
65 return mElapsedTime.elapsed();
66}
67
68void QgsTask::start()
69{
70 QMutexLocker locker( &mNotFinishedMutex );
71 mNotStartedMutex.release();
72 mStartCount++;
73 Q_ASSERT( mStartCount == 1 );
74
75 if ( mStatus != Queued )
76 return;
77
78 mStatus = Running;
79 mOverallStatus = Running;
80 mElapsedTime.start();
81
82 emit statusChanged( Running );
83 emit begun();
84
85 // force initial emission of progressChanged, but respect if task has had initial progress manually set
86 setProgress( mProgress );
87
88 if ( run() )
89 {
90 completed();
91 }
92 else
93 {
94 terminated();
95 }
96}
97
99{
100 if ( mOverallStatus == Complete || mOverallStatus == Terminated )
101 return;
102
103 mShouldTerminateMutex.lock();
104 mShouldTerminate = true;
105 mShouldTerminateMutex.unlock();
106 if ( mStatus == Queued || mStatus == OnHold )
107 {
108 // immediately terminate unstarted jobs
109 terminated();
110 mNotStartedMutex.release();
111 }
112
113 if ( mStatus == Terminated )
114 {
115 processSubTasksForTermination();
116 }
117
118 const auto constMSubTasks = mSubTasks;
119 for ( const SubTask &subTask : constMSubTasks )
120 {
121 subTask.task->cancel();
122 }
123}
124
126{
127 QMutexLocker locker( &mShouldTerminateMutex );
128 return mShouldTerminate;
129}
130
132{
133 if ( mStatus == Queued )
134 {
135 mStatus = OnHold;
136 processSubTasksForHold();
137 }
138
139 const auto constMSubTasks = mSubTasks;
140 for ( const SubTask &subTask : constMSubTasks )
141 {
142 subTask.task->hold();
143 }
144}
145
147{
148 if ( mStatus == OnHold )
149 {
150 mStatus = Queued;
151 mOverallStatus = Queued;
152 emit statusChanged( Queued );
153 }
154
155 const auto constMSubTasks = mSubTasks;
156 for ( const SubTask &subTask : constMSubTasks )
157 {
158 subTask.task->unhold();
159 }
160}
161
162void QgsTask::addSubTask( QgsTask *subTask, const QgsTaskList &dependencies,
163 SubTaskDependency subTaskDependency )
164{
165 mSubTasks << SubTask( subTask, dependencies, subTaskDependency );
166 connect( subTask, &QgsTask::progressChanged, this, [this] { setProgress( mProgress ); } );
167 connect( subTask, &QgsTask::statusChanged, this, &QgsTask::subTaskStatusChanged );
168}
169
170QList<QgsMapLayer *> QgsTask::dependentLayers() const
171{
172 return _qgis_listQPointerToRaw( mDependentLayers );
173}
174
175bool QgsTask::waitForFinished( int timeout )
176{
177 // We wait the task to be started
178 mNotStartedMutex.acquire();
179 mNotStartedMutex.release();
180
181 bool rv = true;
182 if ( mOverallStatus == Complete || mOverallStatus == Terminated )
183 {
184 rv = true;
185 }
186 else
187 {
188 if ( timeout == 0 )
189 timeout = std::numeric_limits< int >::max();
190 if ( mNotFinishedMutex.tryLock( timeout ) )
191 {
192 mNotFinishedMutex.unlock();
193 QCoreApplication::sendPostedEvents( this );
194 rv = true;
195 }
196 else
197 {
198 rv = false;
199 }
200 }
201 return rv;
202}
203
204void QgsTask::setDependentLayers( const QList< QgsMapLayer * > &dependentLayers )
205{
206 mDependentLayers = _qgis_listRawToQPointer( dependentLayers );
207}
208
209void QgsTask::subTaskStatusChanged( int status )
210{
211 QgsTask *subTask = qobject_cast< QgsTask * >( sender() );
212 if ( !subTask )
213 return;
214
215 if ( status == Running && mStatus == Queued )
216 {
217 mOverallStatus = Running;
218 }
219 else if ( status == Complete && mStatus == Complete )
220 {
221 //check again if all subtasks are complete
222 processSubTasksForCompletion();
223 }
224 else if ( ( status == Complete || status == Terminated ) && mStatus == Terminated )
225 {
226 //check again if all subtasks are terminated
227 processSubTasksForTermination();
228 }
229 else if ( ( status == Complete || status == Terminated || status == OnHold ) && mStatus == OnHold )
230 {
231 processSubTasksForHold();
232 }
233 else if ( status == Terminated )
234 {
235 //uh oh...
236 cancel();
237 }
238}
239
241{
242 mProgress = progress;
243
244 if ( !mSubTasks.isEmpty() )
245 {
246 // calculate total progress including subtasks
247
248 double totalProgress = 0.0;
249 const auto constMSubTasks = mSubTasks;
250 for ( const SubTask &subTask : constMSubTasks )
251 {
252 if ( subTask.task->status() == QgsTask::Complete )
253 {
254 totalProgress += 100.0;
255 }
256 else
257 {
258 totalProgress += subTask.task->progress();
259 }
260 }
261 progress = ( progress + totalProgress ) / ( mSubTasks.count() + 1 );
262 }
263
264 // avoid flooding with too many events
265 double prevProgress = mTotalProgress;
266 mTotalProgress = progress;
267
268 // avoid spamming with too many progressChanged reports
269 if ( static_cast< int >( prevProgress * 10 ) != static_cast< int >( mTotalProgress * 10 ) )
271}
272
273void QgsTask::completed()
274{
275 mStatus = Complete;
276 QMetaObject::invokeMethod( this, "processSubTasksForCompletion" );
277}
278
279void QgsTask::processSubTasksForCompletion()
280{
281 bool subTasksCompleted = true;
282 const auto constMSubTasks = mSubTasks;
283 for ( const SubTask &subTask : constMSubTasks )
284 {
285 if ( subTask.task->status() != Complete )
286 {
287 subTasksCompleted = false;
288 break;
289 }
290 }
291
292 if ( mStatus == Complete && subTasksCompleted )
293 {
294 mOverallStatus = Complete;
295
296 setProgress( 100.0 );
297 emit statusChanged( Complete );
298 emit taskCompleted();
299 }
300 else if ( mStatus == Complete )
301 {
302 // defer completion until all subtasks are complete
303 mOverallStatus = Running;
304 }
305}
306
307void QgsTask::processSubTasksForTermination()
308{
309 bool subTasksTerminated = true;
310 const auto constMSubTasks = mSubTasks;
311 for ( const SubTask &subTask : constMSubTasks )
312 {
313 if ( subTask.task->status() != Terminated && subTask.task->status() != Complete )
314 {
315 subTasksTerminated = false;
316 break;
317 }
318 }
319
320 if ( mStatus == Terminated && subTasksTerminated && mOverallStatus != Terminated )
321 {
322 mOverallStatus = Terminated;
323
325 emit taskTerminated();
326 }
327 else if ( mStatus == Terminated && !subTasksTerminated )
328 {
329 // defer termination until all subtasks are terminated (or complete)
330 mOverallStatus = Running;
331 }
332}
333
334void QgsTask::processSubTasksForHold()
335{
336 bool subTasksRunning = false;
337 const auto constMSubTasks = mSubTasks;
338 for ( const SubTask &subTask : constMSubTasks )
339 {
340 if ( subTask.task->status() == Running )
341 {
342 subTasksRunning = true;
343 break;
344 }
345 }
346
347 if ( mStatus == OnHold && !subTasksRunning && mOverallStatus != OnHold )
348 {
349 mOverallStatus = OnHold;
350 emit statusChanged( OnHold );
351 }
352 else if ( mStatus == OnHold && subTasksRunning )
353 {
354 // defer hold until all subtasks finish running
355 mOverallStatus = Running;
356 }
357}
358
359void QgsTask::terminated()
360{
361 mStatus = Terminated;
362 QMetaObject::invokeMethod( this, "processSubTasksForTermination" );
363}
364
365
367
368class QgsTaskRunnableWrapper : public QRunnable
369{
370 public:
371
372 explicit QgsTaskRunnableWrapper( QgsTask *task )
373 : mTask( task )
374 {
375 setAutoDelete( true );
376 }
377
378 void run() override
379 {
380 Q_ASSERT( mTask );
381 mTask->start();
382 }
383
384 private:
385
386 QgsTask *mTask = nullptr;
387
388};
389
391
392
393
394//
395// QgsTaskManager
396//
397
399 : QObject( parent )
400 , mThreadPool( new QThreadPool( this ) )
401 , mTaskMutex( new QRecursiveMutex() )
402{
403
404}
405
407{
408 //first tell all tasks to cancel
409 cancelAll();
410
411 //then clean them up, including waiting for them to terminate
412 mTaskMutex->lock();
413 QMap< long, TaskInfo > tasks = mTasks;
414 mTasks.detach();
415 mTaskMutex->unlock();
416 QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
417 for ( ; it != tasks.constEnd(); ++it )
418 {
419 cleanupAndDeleteTask( it.value().task );
420 }
421
422 delete mTaskMutex;
423 mThreadPool->waitForDone();
424}
425
427{
428 return mThreadPool;
429}
430
432{
433 return addTaskPrivate( task, QgsTaskList(), false, priority );
434}
435
436long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
437{
438 return addTaskPrivate( definition.task,
439 definition.dependentTasks,
440 false,
441 priority );
442}
443
444
445long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
446{
447 if ( !task )
448 return 0;
449
450 if ( !mInitialized )
451 {
452 mInitialized = true;
453 // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
454 // cos that forces early creation of QgsProject
455 connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ), // skip-keyword-check
456 this, &QgsTaskManager::layersWillBeRemoved );
457 }
458
459 long taskId = mNextTaskId++;
460
461 mTaskMutex->lock();
462 mTasks.insert( taskId, TaskInfo( task, priority ) );
463 mMapTaskPtrToId[task] = taskId;
464 if ( isSubTask )
465 {
466 mSubTasks << task;
467 }
468 else
469 {
470 mParentTasks << task;
471 }
472 if ( !task->dependentLayers().isEmpty() )
473 mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
474 mTaskMutex->unlock();
475
476 connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
477 if ( !isSubTask )
478 {
479 connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
480 }
481
482 // add all subtasks, must be done before dependency resolution
483 for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
484 {
485 switch ( subTask.dependency )
486 {
488 dependencies << subTask.task;
489 break;
490
492 //nothing
493 break;
494 }
495 //recursively add sub tasks
496 addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
497 }
498
499 if ( !dependencies.isEmpty() )
500 {
501 mTaskDependencies.insert( taskId, dependencies );
502 }
503
504 if ( hasCircularDependencies( taskId ) )
505 {
506 task->cancel();
507 }
508
509 if ( !isSubTask )
510 {
511 if ( !( task->flags() & QgsTask::Hidden ) )
512 emit taskAdded( taskId );
513
514 processQueue();
515 }
516
517 return taskId;
518}
519
521{
522 QMutexLocker ml( mTaskMutex );
523 QgsTask *t = nullptr;
524 if ( mTasks.contains( id ) )
525 t = mTasks.value( id ).task;
526 return t;
527}
528
529QList<QgsTask *> QgsTaskManager::tasks() const
530{
531 QMutexLocker ml( mTaskMutex );
532 return QList<QgsTask *>( mParentTasks.begin(), mParentTasks.end() );
533}
534
536{
537 QMutexLocker ml( mTaskMutex );
538 return mParentTasks.count();
539}
540
542{
543 if ( !task )
544 return -1;
545
546 QMutexLocker ml( mTaskMutex );
547 const auto iter = mMapTaskPtrToId.constFind( task );
548 if ( iter != mMapTaskPtrToId.constEnd() )
549 return *iter;
550 return -1;
551}
552
554{
555 mTaskMutex->lock();
556 QSet< QgsTask * > parents = mParentTasks;
557 parents.detach();
558 mTaskMutex->unlock();
559
560 const auto constParents = parents;
561 for ( QgsTask *task : constParents )
562 {
563 task->cancel();
564 }
565}
566
568{
569 mTaskMutex->lock();
570 QMap< long, QgsTaskList > dependencies = mTaskDependencies;
571 dependencies.detach();
572 mTaskMutex->unlock();
573
574 if ( !dependencies.contains( taskId ) )
575 return true;
576
577 const auto constValue = dependencies.value( taskId );
578 for ( QgsTask *task : constValue )
579 {
580 if ( task->status() != QgsTask::Complete )
581 return false;
582 }
583
584 return true;
585}
586
587QSet<long> QgsTaskManager::dependencies( long taskId ) const
588{
589 QSet<long> results;
590 if ( resolveDependencies( taskId, results ) )
591 return results;
592 else
593 return QSet<long>();
594}
595
596bool QgsTaskManager::resolveDependencies( long thisTaskId, QSet<long> &results ) const
597{
598 mTaskMutex->lock();
599 QMap< long, QgsTaskList > dependencies = mTaskDependencies;
600 dependencies.detach();
601 mTaskMutex->unlock();
602
603 QSet<long> alreadyExploredTaskIds;
604 QStack<long> stackTaskIds;
605 stackTaskIds.push( thisTaskId );
606 while ( !stackTaskIds.isEmpty() )
607 {
608 const long currentTaskId = stackTaskIds.pop();
609 alreadyExploredTaskIds.insert( currentTaskId );
610
611 auto iter = dependencies.constFind( currentTaskId );
612 if ( iter == dependencies.constEnd() )
613 continue;
614
615 const auto &constValue = *iter;
616 for ( QgsTask *task : constValue )
617 {
618 const long dependentTaskId = taskId( task );
619 if ( dependentTaskId >= 0 )
620 {
621 if ( thisTaskId == dependentTaskId )
622 {
623 // circular dependencies
624 return false;
625 }
626
627 //add task as dependent
628 results.insert( dependentTaskId );
629
630 // and add it to the stack of tasks whose dependencies must be resolved
631 if ( !alreadyExploredTaskIds.contains( dependentTaskId ) )
632 {
633 stackTaskIds.push( dependentTaskId );
634 }
635 }
636 }
637 }
638
639 return true;
640}
641
642bool QgsTaskManager::hasCircularDependencies( long taskId ) const
643{
644 QSet< long > d;
645 return !resolveDependencies( taskId, d );
646}
647
648QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
649{
650 QMutexLocker ml( mTaskMutex );
651 return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
652}
653
655{
656 QMutexLocker ml( mTaskMutex );
657 QList< QgsTask * > tasks;
658 QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
659 for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
660 {
661 if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
662 {
663 QgsTask *layerTask = task( layerIt.key() );
664 if ( layerTask )
665 tasks << layerTask;
666 }
667 }
668 return tasks;
669}
670
671QList<QgsTask *> QgsTaskManager::activeTasks() const
672{
673 QMutexLocker ml( mTaskMutex );
674 QSet< QgsTask * > activeTasks = mActiveTasks;
675 activeTasks.intersect( mParentTasks );
676 return QList<QgsTask *>( activeTasks.constBegin(), activeTasks.constEnd() );
677}
678
679int QgsTaskManager::countActiveTasks( bool includeHidden ) const
680{
681 QMutexLocker ml( mTaskMutex );
682 QSet< QgsTask * > tasks = mActiveTasks;
683
684 if ( !includeHidden )
685 {
686 QSet< QgsTask * > filteredTasks;
687 filteredTasks.reserve( tasks.size() );
688 for ( QgsTask *task : tasks )
689 {
690 if ( !( task->flags() & QgsTask::Hidden ) )
691 filteredTasks.insert( task );
692 }
693 tasks = filteredTasks;
694 }
695
696 return tasks.intersect( mParentTasks ).count();
697}
698
700{
701 if ( task )
702 emit taskTriggered( task );
703}
704
705void QgsTaskManager::taskProgressChanged( double progress )
706{
707 QgsTask *task = qobject_cast< QgsTask * >( sender() );
708 if ( task && task->flags() & QgsTask::Hidden )
709 return;
710
711 //find ID of task
712 long id = taskId( task );
713 if ( id < 0 )
714 return;
715
716 emit progressChanged( id, progress );
717
718 if ( countActiveTasks( false ) == 1 )
719 {
720 emit finalTaskProgressChanged( progress );
721 }
722}
723
724void QgsTaskManager::taskStatusChanged( int status )
725{
726 QgsTask *task = qobject_cast< QgsTask * >( sender() );
727 const bool isHidden = task && task->flags() & QgsTask::Hidden;
728
729 //find ID of task
730 long id = taskId( task );
731 if ( id < 0 )
732 return;
733
734 mTaskMutex->lock();
735 QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
736 mTaskMutex->unlock();
737 if ( runnable && mThreadPool->tryTake( runnable ) )
738 {
739 delete runnable;
740 mTasks[ id ].runnable = nullptr;
741 }
742
743 if ( status == QgsTask::Terminated || status == QgsTask::Complete )
744 {
745 bool result = status == QgsTask::Complete;
746 task->finished( result );
747 }
748
749 if ( status == QgsTask::Terminated )
750 {
751 //recursively cancel dependent tasks
752 cancelDependentTasks( id );
753 }
754
755 mTaskMutex->lock();
756 bool isParent = mParentTasks.contains( task );
757 mTaskMutex->unlock();
758 if ( isParent && !isHidden )
759 {
760 // don't emit status changed for subtasks
761 emit statusChanged( id, status );
762 }
763
764 processQueue();
765
766 if ( status == QgsTask::Terminated || status == QgsTask::Complete )
767 {
768 cleanupAndDeleteTask( task );
769 }
770
771}
772
773void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
774{
775 mTaskMutex->lock();
776 // scan through layers to be removed
777 QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
778 layerDependencies.detach();
779 mTaskMutex->unlock();
780
781 const auto constLayers = layers;
782 for ( QgsMapLayer *layer : constLayers )
783 {
784 // scan through tasks with layer dependencies
785 for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
786 it != layerDependencies.constEnd(); ++it )
787 {
788 if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
789 {
790 //task not dependent on this layer
791 continue;
792 }
793
794 QgsTask *dependentTask = task( it.key() );
795 if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
796 {
797 // incomplete task is dependent on this layer!
798 dependentTask->cancel();
799 }
800 }
801 }
802}
803
804
805bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
806{
807 if ( !task )
808 return false;
809
810 long id = taskId( task );
811 if ( id < 0 )
812 return false;
813
814 QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
815
816 task->disconnect( this );
817
818 mTaskMutex->lock();
819 if ( mTaskDependencies.contains( id ) )
820 mTaskDependencies.remove( id );
821 mTaskMutex->unlock();
822
823 emit taskAboutToBeDeleted( id );
824
825 mTaskMutex->lock();
826 bool isParent = mParentTasks.contains( task );
827 mParentTasks.remove( task );
828 mSubTasks.remove( task );
829 mTasks.remove( id );
830 mMapTaskPtrToId.remove( task );
831 mLayerDependencies.remove( id );
832
833 if ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated )
834 {
835 if ( isParent )
836 {
837 // delete task when it's terminated
838 connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
839 connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
840 }
841 task->cancel();
842 }
843 else
844 {
845 if ( runnable && mThreadPool->tryTake( runnable ) )
846 {
847 delete runnable;
848 mTasks[ id ].runnable = nullptr;
849 }
850
851 if ( isParent )
852 {
853 //task already finished, kill it
854 task->deleteLater();
855 }
856 }
857
858 // at this stage (hopefully) dependent tasks have been canceled or queued
859 for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
860 {
861 if ( it.value().contains( task ) )
862 {
863 it.value().removeAll( task );
864 }
865 }
866 mTaskMutex->unlock();
867
868 return true;
869}
870
871void QgsTaskManager::processQueue()
872{
873 int prevActiveCount = countActiveTasks( false );
874 mTaskMutex->lock();
875 mActiveTasks.clear();
876 for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
877 {
878 QgsTask *task = it.value().task;
879 if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
880 {
881 it.value().createRunnable();
882 mThreadPool->start( it.value().runnable, it.value().priority );
883 }
884
885 if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
886 {
887 mActiveTasks << task;
888 }
889 }
890
891 bool allFinished = mActiveTasks.isEmpty();
892 mTaskMutex->unlock();
893
894 if ( allFinished )
895 {
896 emit allTasksFinished();
897 }
898
899 int newActiveCount = countActiveTasks( false );
900 if ( prevActiveCount != newActiveCount )
901 {
902 emit countActiveTasksChanged( newActiveCount );
903 }
904}
905
906void QgsTaskManager::cancelDependentTasks( long taskId )
907{
908 QgsTask *canceledTask = task( taskId );
909
910 //deep copy
911 mTaskMutex->lock();
912 QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
913 taskDependencies.detach();
914 mTaskMutex->unlock();
915
916 QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
917 for ( ; it != taskDependencies.constEnd(); ++it )
918 {
919 if ( it.value().contains( canceledTask ) )
920 {
921 // found task with this dependency
922
923 // cancel it - note that this will be recursive, so any tasks dependent
924 // on this one will also be canceled
925 QgsTask *dependentTask = task( it.key() );
926 if ( dependentTask )
927 dependentTask->cancel();
928 }
929 }
930}
931
932QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
933 : task( task )
934 , added( 0 )
935 , priority( priority )
936{}
937
938void QgsTaskManager::TaskInfo::createRunnable()
939{
940 Q_ASSERT( !runnable );
941 runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
942}
943
944
946{
947 for ( QgsTask *subTask : mSubTasksSerial )
948 {
949 delete subTask;
950 }
951}
952
954{
955 mSubTasksSerial << subTask;
956}
957
959{
960 size_t i = 0;
961 for ( QgsTask *subTask : mSubTasksSerial )
962 {
963 if ( mShouldTerminate )
964 return false;
965 connect( subTask, &QgsTask::progressChanged, this,
966 [this, i]( double subTaskProgress )
967 {
968 mProgress = 100.0 * ( double( i ) + subTaskProgress / 100.0 ) / double( mSubTasksSerial.size() );
969 setProgress( mProgress );
970 } );
971 if ( !subTask->run() )
972 return false;
973 subTask->completed();
974 mProgress = 100.0 * double( i + 1 ) / double( mSubTasksSerial.size() );
975 setProgress( mProgress );
976 ++i;
977 }
978 return true;
979}
980
982{
983 if ( mOverallStatus == Complete || mOverallStatus == Terminated )
984 return;
985
987
988 for ( QgsTask *subTask : mSubTasksSerial )
989 {
990 subTask->cancel();
991 }
992}
Base class for all map layer types.
Definition qgsmaplayer.h:83
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
static QgsProject * instance()
Returns the QgsProject singleton instance.
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.
QThreadPool * threadPool()
Returns the threadpool utilized by the task manager.
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.
QList< QgsTask * > mSubTasksSerial
void addSubTask(QgsTask *subTask)
Add a subtask and transfer its ownership.
void cancel() override
Notifies the task that it should terminate.
bool run() override
Performs the task's operation.
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).
~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.
QFlags< Flag > Flags
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.
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.
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.