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