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