QGIS API Documentation 4.1.0-Master (60fea48833c)
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 ( !task->moveToThread( this->thread() ) )
447 {
448 QgsDebugError( u"Failed to move task \"%1\" from background thread to task manager thread"_s.arg( task->description() ) );
449 }
450 }
451
452 if ( !mInitialized )
453 {
454 mInitialized = true;
455 // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
456 // cos that forces early creation of QgsProject
457 connect(
458 QgsProject::instance(), // skip-keyword-check
459 static_cast< void ( QgsProject::* )( const QList< QgsMapLayer * > & ) >( &QgsProject::layersWillBeRemoved ),
460 this,
461 &QgsTaskManager::layersWillBeRemoved
462 );
463 }
464
465 long taskId = mNextTaskId++;
466
467 mTaskMutex->lock();
468 mTasks.insert( taskId, TaskInfo( task, priority ) );
469 mMapTaskPtrToId[task] = taskId;
470 if ( isSubTask )
471 {
472 mSubTasks << task;
473 }
474 else
475 {
476 mParentTasks << task;
477 }
478 if ( !task->dependentLayers().isEmpty() )
479 mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
480 mTaskMutex->unlock();
481
482 connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
483 if ( !isSubTask )
484 {
485 connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
486 }
487
488 // add all subtasks, must be done before dependency resolution
489 for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
490 {
491 switch ( subTask.dependency )
492 {
494 dependencies << subTask.task;
495 break;
496
498 //nothing
499 break;
500 }
501 //recursively add sub tasks
502 addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
503 }
504
505 if ( !dependencies.isEmpty() )
506 {
507 mTaskDependencies.insert( taskId, dependencies );
508 }
509
510 if ( hasCircularDependencies( taskId ) )
511 {
512 task->cancel();
513 }
514
515 if ( !isSubTask )
516 {
517 if ( !( task->flags() & QgsTask::Hidden ) )
518 emit taskAdded( taskId );
519
520 processQueue();
521 }
522
523 return taskId;
524}
525
527{
528 QMutexLocker ml( mTaskMutex );
529 QgsTask *t = nullptr;
530 if ( mTasks.contains( id ) )
531 t = mTasks.value( id ).task;
532 return t;
533}
534
535QList<QgsTask *> QgsTaskManager::tasks() const
536{
537 QMutexLocker ml( mTaskMutex );
538 return QList<QgsTask *>( mParentTasks.begin(), mParentTasks.end() );
539}
540
542{
543 QMutexLocker ml( mTaskMutex );
544 return mParentTasks.count();
545}
546
548{
549 if ( !task )
550 return -1;
551
552 QMutexLocker ml( mTaskMutex );
553 const auto iter = mMapTaskPtrToId.constFind( task );
554 if ( iter != mMapTaskPtrToId.constEnd() )
555 return *iter;
556 return -1;
557}
558
560{
561 mTaskMutex->lock();
562 QSet< QgsTask * > parents = mParentTasks;
563 parents.detach();
564 mTaskMutex->unlock();
565
566 const auto constParents = parents;
567 for ( QgsTask *task : constParents )
568 {
569 task->cancel();
570 }
571}
572
574{
575 mTaskMutex->lock();
576 QMap< long, QgsTaskList > dependencies = mTaskDependencies;
577 dependencies.detach();
578 mTaskMutex->unlock();
579
580 if ( !dependencies.contains( taskId ) )
581 return true;
582
583 const auto constValue = dependencies.value( taskId );
584 for ( QgsTask *task : constValue )
585 {
586 if ( task->status() != QgsTask::Complete )
587 return false;
588 }
589
590 return true;
591}
592
593QSet<long> QgsTaskManager::dependencies( long taskId ) const
594{
595 QSet<long> results;
596 if ( resolveDependencies( taskId, results ) )
597 return results;
598 else
599 return QSet<long>();
600}
601
602bool QgsTaskManager::resolveDependencies( long thisTaskId, QSet<long> &results ) const
603{
604 mTaskMutex->lock();
605 QMap< long, QgsTaskList > dependencies = mTaskDependencies;
606 dependencies.detach();
607 mTaskMutex->unlock();
608
609 QSet<long> alreadyExploredTaskIds;
610 QStack<long> stackTaskIds;
611 stackTaskIds.push( thisTaskId );
612 while ( !stackTaskIds.isEmpty() )
613 {
614 const long currentTaskId = stackTaskIds.pop();
615 alreadyExploredTaskIds.insert( currentTaskId );
616
617 auto iter = dependencies.constFind( currentTaskId );
618 if ( iter == dependencies.constEnd() )
619 continue;
620
621 const auto &constValue = *iter;
622 for ( QgsTask *task : constValue )
623 {
624 const long dependentTaskId = taskId( task );
625 if ( dependentTaskId >= 0 )
626 {
627 if ( thisTaskId == dependentTaskId )
628 {
629 // circular dependencies
630 return false;
631 }
632
633 //add task as dependent
634 results.insert( dependentTaskId );
635
636 // and add it to the stack of tasks whose dependencies must be resolved
637 if ( !alreadyExploredTaskIds.contains( dependentTaskId ) )
638 {
639 stackTaskIds.push( dependentTaskId );
640 }
641 }
642 }
643 }
644
645 return true;
646}
647
648bool QgsTaskManager::hasCircularDependencies( long taskId ) const
649{
650 QSet< long > d;
651 return !resolveDependencies( taskId, d );
652}
653
654QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
655{
656 QMutexLocker ml( mTaskMutex );
657 return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
658}
659
661{
662 QMutexLocker ml( mTaskMutex );
663 QList< QgsTask * > tasks;
664 QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
665 for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
666 {
667 if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
668 {
669 QgsTask *layerTask = task( layerIt.key() );
670 if ( layerTask )
671 tasks << layerTask;
672 }
673 }
674 return tasks;
675}
676
677QList<QgsTask *> QgsTaskManager::activeTasks() const
678{
679 QMutexLocker ml( mTaskMutex );
680 QSet< QgsTask * > activeTasks = mActiveTasks;
681 activeTasks.intersect( mParentTasks );
682 return QList<QgsTask *>( activeTasks.constBegin(), activeTasks.constEnd() );
683}
684
685int QgsTaskManager::countActiveTasks( bool includeHidden ) const
686{
687 QMutexLocker ml( mTaskMutex );
688 QSet< QgsTask * > tasks = mActiveTasks;
689
690 if ( !includeHidden )
691 {
692 QSet< QgsTask * > filteredTasks;
693 filteredTasks.reserve( tasks.size() );
694 for ( QgsTask *task : tasks )
695 {
696 if ( !( task->flags() & QgsTask::Hidden ) )
697 filteredTasks.insert( task );
698 }
699 tasks = filteredTasks;
700 }
701
702 return tasks.intersect( mParentTasks ).count();
703}
704
706{
707 if ( task )
708 emit taskTriggered( task );
709}
710
711void QgsTaskManager::taskProgressChanged( double progress )
712{
713 QgsTask *task = qobject_cast< QgsTask * >( sender() );
714 if ( task && task->flags() & QgsTask::Hidden )
715 return;
716
717 //find ID of task
718 long id = taskId( task );
719 if ( id < 0 )
720 return;
721
722 emit progressChanged( id, progress );
723
724 if ( countActiveTasks( false ) == 1 )
725 {
726 emit finalTaskProgressChanged( progress );
727 }
728}
729
730void QgsTaskManager::taskStatusChanged( int status )
731{
732 QgsTask *task = qobject_cast< QgsTask * >( sender() );
733 const bool isHidden = task && task->flags() & QgsTask::Hidden;
734
735 //find ID of task
736 long id = taskId( task );
737 if ( id < 0 )
738 return;
739
740 mTaskMutex->lock();
741 QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
742 mTaskMutex->unlock();
743 if ( runnable && mThreadPool->tryTake( runnable ) )
744 {
745 delete runnable;
746 mTasks[id].runnable = nullptr;
747 }
748
749 if ( status == QgsTask::Terminated || status == QgsTask::Complete )
750 {
751 bool result = status == QgsTask::Complete;
752 task->finished( result );
753 }
754
755 if ( status == QgsTask::Terminated )
756 {
757 //recursively cancel dependent tasks
758 cancelDependentTasks( id );
759 }
760
761 mTaskMutex->lock();
762 bool isParent = mParentTasks.contains( task );
763 mTaskMutex->unlock();
764 if ( isParent && !isHidden )
765 {
766 // don't emit status changed for subtasks
767 emit statusChanged( id, status );
768 }
769
770 processQueue();
771
772 if ( status == QgsTask::Terminated || status == QgsTask::Complete )
773 {
774 cleanupAndDeleteTask( task );
775 }
776}
777
778void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
779{
780 mTaskMutex->lock();
781 // scan through layers to be removed
782 QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
783 layerDependencies.detach();
784 mTaskMutex->unlock();
785
786 const auto constLayers = layers;
787 for ( QgsMapLayer *layer : constLayers )
788 {
789 // scan through tasks with layer dependencies
790 for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin(); it != layerDependencies.constEnd(); ++it )
791 {
792 if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
793 {
794 //task not dependent on this layer
795 continue;
796 }
797
798 QgsTask *dependentTask = task( it.key() );
799 if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
800 {
801 // incomplete task is dependent on this layer!
802 dependentTask->cancel();
803 }
804 }
805 }
806}
807
808
809bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
810{
811 if ( !task )
812 return false;
813
814 long id = taskId( task );
815 if ( id < 0 )
816 return false;
817
818 QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
819
820 task->disconnect( this );
821
822 mTaskMutex->lock();
823 if ( mTaskDependencies.contains( id ) )
824 mTaskDependencies.remove( id );
825 mTaskMutex->unlock();
826
827 emit taskAboutToBeDeleted( id );
828
829 mTaskMutex->lock();
830 bool isParent = mParentTasks.contains( task );
831 mParentTasks.remove( task );
832 mSubTasks.remove( task );
833 mTasks.remove( id );
834 mMapTaskPtrToId.remove( task );
835 mLayerDependencies.remove( id );
836
837 if ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated )
838 {
839 if ( isParent )
840 {
841 // delete task when it's terminated
842 connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
843 connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
844 }
845 task->cancel();
846 }
847 else
848 {
849 if ( runnable && mThreadPool->tryTake( runnable ) )
850 {
851 delete runnable;
852 mTasks[id].runnable = nullptr;
853 }
854
855 if ( isParent )
856 {
857 //task already finished, kill it
858 task->deleteLater();
859 }
860 }
861
862 // at this stage (hopefully) dependent tasks have been canceled or queued
863 for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
864 {
865 if ( it.value().contains( task ) )
866 {
867 it.value().removeAll( task );
868 }
869 }
870 mTaskMutex->unlock();
871
872 return true;
873}
874
875void QgsTaskManager::processQueue()
876{
877 int prevActiveCount = countActiveTasks( false );
878 mTaskMutex->lock();
879 mActiveTasks.clear();
880 for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
881 {
882 QgsTask *task = it.value().task;
883 if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
884 {
885 it.value().createRunnable();
886 mThreadPool->start( it.value().runnable, it.value().priority );
887 }
888
889 if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
890 {
891 mActiveTasks << task;
892 }
893 }
894
895 bool allFinished = mActiveTasks.isEmpty();
896 mTaskMutex->unlock();
897
898 if ( allFinished )
899 {
900 emit allTasksFinished();
901 }
902
903 int newActiveCount = countActiveTasks( false );
904 if ( prevActiveCount != newActiveCount )
905 {
906 emit countActiveTasksChanged( newActiveCount );
907 }
908}
909
910void QgsTaskManager::cancelDependentTasks( long taskId )
911{
912 QgsTask *canceledTask = task( taskId );
913
914 //deep copy
915 mTaskMutex->lock();
916 QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
917 taskDependencies.detach();
918 mTaskMutex->unlock();
919
920 QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
921 for ( ; it != taskDependencies.constEnd(); ++it )
922 {
923 if ( it.value().contains( canceledTask ) )
924 {
925 // found task with this dependency
926
927 // cancel it - note that this will be recursive, so any tasks dependent
928 // on this one will also be canceled
929 QgsTask *dependentTask = task( it.key() );
930 if ( dependentTask )
931 dependentTask->cancel();
932 }
933 }
934}
935
936QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
937 : task( task )
938 , added( 0 )
939 , priority( priority )
940{}
941
942void QgsTaskManager::TaskInfo::createRunnable()
943{
944 Q_ASSERT( !runnable );
945 runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
946}
947
948
950{
951 for ( QgsTask *subTask : mSubTasksSerial )
952 {
953 delete subTask;
954 }
955}
956
958{
959 mSubTasksSerial << subTask;
960}
961
963{
964 size_t i = 0;
965 for ( QgsTask *subTask : mSubTasksSerial )
966 {
967 if ( mShouldTerminate )
968 return false;
969 connect( subTask, &QgsTask::progressChanged, this, [this, i]( double subTaskProgress ) {
970 mProgress = 100.0 * ( double( i ) + subTaskProgress / 100.0 ) / double( mSubTasksSerial.size() );
971 setProgress( mProgress );
972 } );
973 if ( !subTask->run() )
974 return false;
975 subTask->completed();
976 mProgress = 100.0 * double( i + 1 ) / double( mSubTasksSerial.size() );
977 setProgress( mProgress );
978 ++i;
979 }
980 return true;
981}
982
984{
985 if ( mOverallStatus == Complete || mOverallStatus == Terminated )
986 return;
987
989
990 for ( QgsTask *subTask : mSubTasksSerial )
991 {
992 subTask->cancel();
993 }
994}
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.