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