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