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