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