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 : 5 : , mTaskMutex( new QMutex( QMutex::Recursive ) )
393 : 10 : {
394 : :
395 : 5 : }
396 : :
397 : 10 : QgsTaskManager::~QgsTaskManager()
398 : 10 : {
399 : : //first tell all tasks to cancel
400 : 5 : cancelAll();
401 : :
402 : : //then clean them up, including waiting for them to terminate
403 : 5 : mTaskMutex->lock();
404 : 5 : QMap< long, TaskInfo > tasks = mTasks;
405 : 5 : mTasks.detach();
406 : 5 : mTaskMutex->unlock();
407 : 5 : QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
408 : 5 : for ( ; it != tasks.constEnd(); ++it )
409 : : {
410 : 0 : cleanupAndDeleteTask( it.value().task );
411 : 0 : }
412 : :
413 : 5 : delete mTaskMutex;
414 : 10 : }
415 : :
416 : 0 : long QgsTaskManager::addTask( QgsTask *task, int priority )
417 : : {
418 : 0 : return addTaskPrivate( task, QgsTaskList(), false, priority );
419 : 0 : }
420 : :
421 : 0 : long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
422 : : {
423 : 0 : return addTaskPrivate( definition.task,
424 : 0 : definition.dependentTasks,
425 : : false,
426 : 0 : priority );
427 : 0 : }
428 : :
429 : :
430 : 0 : long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
431 : : {
432 : 0 : if ( !task )
433 : 0 : return 0;
434 : :
435 : 0 : if ( !mInitialized )
436 : : {
437 : 0 : mInitialized = true;
438 : : // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
439 : : // cos that forces early creation of QgsProject
440 : 0 : connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ),
441 : : this, &QgsTaskManager::layersWillBeRemoved );
442 : 0 : }
443 : :
444 : 0 : long taskId = mNextTaskId++;
445 : :
446 : 0 : mTaskMutex->lock();
447 : 0 : mTasks.insert( taskId, TaskInfo( task, priority ) );
448 : 0 : if ( isSubTask )
449 : : {
450 : 0 : mSubTasks << task;
451 : 0 : }
452 : : else
453 : : {
454 : 0 : mParentTasks << task;
455 : : }
456 : 0 : if ( !task->dependentLayers().isEmpty() )
457 : 0 : mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
458 : 0 : mTaskMutex->unlock();
459 : :
460 : 0 : connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
461 : 0 : if ( !isSubTask )
462 : : {
463 : 0 : connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
464 : 0 : }
465 : :
466 : : // add all subtasks, must be done before dependency resolution
467 : 0 : for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
468 : : {
469 : 0 : switch ( subTask.dependency )
470 : : {
471 : : case QgsTask::ParentDependsOnSubTask:
472 : 0 : dependencies << subTask.task;
473 : 0 : break;
474 : :
475 : : case QgsTask::SubTaskIndependent:
476 : : //nothing
477 : 0 : break;
478 : : }
479 : : //recursively add sub tasks
480 : 0 : addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
481 : : }
482 : :
483 : 0 : if ( !dependencies.isEmpty() )
484 : : {
485 : 0 : mTaskDependencies.insert( taskId, dependencies );
486 : 0 : }
487 : :
488 : 0 : if ( hasCircularDependencies( taskId ) )
489 : : {
490 : 0 : task->cancel();
491 : 0 : }
492 : :
493 : 0 : if ( !isSubTask )
494 : : {
495 : 0 : emit taskAdded( taskId );
496 : 0 : processQueue();
497 : 0 : }
498 : :
499 : 0 : return taskId;
500 : 0 : }
501 : :
502 : 0 : QgsTask *QgsTaskManager::task( long id ) const
503 : : {
504 : 0 : QMutexLocker ml( mTaskMutex );
505 : 0 : QgsTask *t = nullptr;
506 : 0 : if ( mTasks.contains( id ) )
507 : 0 : t = mTasks.value( id ).task;
508 : 0 : return t;
509 : 0 : }
510 : :
511 : 0 : QList<QgsTask *> QgsTaskManager::tasks() const
512 : : {
513 : 0 : QMutexLocker ml( mTaskMutex );
514 : 0 : return qgis::setToList( mParentTasks );
515 : 0 : }
516 : :
517 : 0 : int QgsTaskManager::count() const
518 : : {
519 : 0 : QMutexLocker ml( mTaskMutex );
520 : 0 : return mParentTasks.count();
521 : 0 : }
522 : :
523 : 0 : long QgsTaskManager::taskId( QgsTask *task ) const
524 : : {
525 : 0 : if ( !task )
526 : 0 : return -1;
527 : :
528 : 0 : QMutexLocker ml( mTaskMutex );
529 : 0 : QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
530 : 0 : for ( ; it != mTasks.constEnd(); ++it )
531 : : {
532 : 0 : if ( it.value().task == task )
533 : : {
534 : 0 : return it.key();
535 : : }
536 : 0 : }
537 : 0 : return -1;
538 : 0 : }
539 : :
540 : 5 : void QgsTaskManager::cancelAll()
541 : : {
542 : 5 : mTaskMutex->lock();
543 : 5 : QSet< QgsTask * > parents = mParentTasks;
544 : 5 : parents.detach();
545 : 5 : mTaskMutex->unlock();
546 : :
547 : 5 : const auto constParents = parents;
548 : 5 : for ( QgsTask *task : constParents )
549 : : {
550 : 0 : task->cancel();
551 : : }
552 : 5 : }
553 : :
554 : 0 : bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
555 : : {
556 : 0 : mTaskMutex->lock();
557 : 0 : QMap< long, QgsTaskList > dependencies = mTaskDependencies;
558 : 0 : dependencies.detach();
559 : 0 : mTaskMutex->unlock();
560 : :
561 : 0 : if ( !dependencies.contains( taskId ) )
562 : 0 : return true;
563 : :
564 : 0 : const auto constValue = dependencies.value( taskId );
565 : 0 : for ( QgsTask *task : constValue )
566 : : {
567 : 0 : if ( task->status() != QgsTask::Complete )
568 : 0 : return false;
569 : : }
570 : :
571 : 0 : return true;
572 : 0 : }
573 : :
574 : 0 : QSet<long> QgsTaskManager::dependencies( long taskId ) const
575 : : {
576 : 0 : QSet<long> results;
577 : 0 : if ( resolveDependencies( taskId, taskId, results ) )
578 : 0 : return results;
579 : : else
580 : 0 : return QSet<long>();
581 : 0 : }
582 : :
583 : 0 : bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
584 : : {
585 : 0 : mTaskMutex->lock();
586 : 0 : QMap< long, QgsTaskList > dependencies = mTaskDependencies;
587 : 0 : dependencies.detach();
588 : 0 : mTaskMutex->unlock();
589 : :
590 : 0 : if ( !dependencies.contains( currentTaskId ) )
591 : 0 : return true;
592 : :
593 : 0 : const auto constValue = dependencies.value( currentTaskId );
594 : 0 : for ( QgsTask *task : constValue )
595 : 0 : {
596 : 0 : long dependentTaskId = taskId( task );
597 : 0 : if ( dependentTaskId >= 0 )
598 : 5 : {
599 : 0 : if ( dependentTaskId == firstTaskId )
600 : : // circular
601 : 0 : return false;
602 : :
603 : : //add task as dependent
604 : 0 : results.insert( dependentTaskId );
605 : : //plus all its other dependencies
606 : 0 : QSet< long > newTaskDeps;
607 : 5 : if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
608 : 0 : return false;
609 : :
610 : 0 : if ( newTaskDeps.contains( firstTaskId ) )
611 : : {
612 : : // circular
613 : 0 : return false;
614 : : }
615 : :
616 : 0 : results.unite( newTaskDeps );
617 : 0 : }
618 : : }
619 : :
620 : 0 : return true;
621 : 0 : }
622 : :
623 : 0 : bool QgsTaskManager::hasCircularDependencies( long taskId ) const
624 : : {
625 : 0 : QSet< long > d;
626 : 0 : return !resolveDependencies( taskId, taskId, d );
627 : 0 : }
628 : :
629 : 0 : QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
630 : : {
631 : 0 : QMutexLocker ml( mTaskMutex );
632 : 0 : return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
633 : 0 : }
634 : :
635 : 0 : QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
636 : : {
637 : 0 : QMutexLocker ml( mTaskMutex );
638 : 0 : QList< QgsTask * > tasks;
639 : 0 : QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
640 : 0 : for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
641 : : {
642 : 0 : if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
643 : : {
644 : 0 : QgsTask *layerTask = task( layerIt.key() );
645 : 0 : if ( layerTask )
646 : 0 : tasks << layerTask;
647 : 0 : }
648 : 0 : }
649 : 0 : return tasks;
650 : 0 : }
651 : :
652 : 0 : QList<QgsTask *> QgsTaskManager::activeTasks() const
653 : : {
654 : 0 : QMutexLocker ml( mTaskMutex );
655 : 0 : QSet< QgsTask * > activeTasks = mActiveTasks;
656 : 0 : activeTasks.intersect( mParentTasks );
657 : 0 : return qgis::setToList( activeTasks );
658 : 0 : }
659 : :
660 : 0 : int QgsTaskManager::countActiveTasks() const
661 : : {
662 : 0 : QMutexLocker ml( mTaskMutex );
663 : 0 : QSet< QgsTask * > tasks = mActiveTasks;
664 : 0 : return tasks.intersect( mParentTasks ).count();
665 : 0 : }
666 : :
667 : 0 : void QgsTaskManager::triggerTask( QgsTask *task )
668 : : {
669 : 0 : if ( task )
670 : 0 : emit taskTriggered( task );
671 : 0 : }
672 : :
673 : 0 : void QgsTaskManager::taskProgressChanged( double progress )
674 : : {
675 : 0 : QgsTask *task = qobject_cast< QgsTask * >( sender() );
676 : :
677 : : //find ID of task
678 : 0 : long id = taskId( task );
679 : 0 : if ( id < 0 )
680 : 0 : return;
681 : :
682 : 0 : emit progressChanged( id, progress );
683 : :
684 : 0 : if ( countActiveTasks() == 1 )
685 : : {
686 : 0 : emit finalTaskProgressChanged( progress );
687 : 0 : }
688 : 0 : }
689 : :
690 : 0 : void QgsTaskManager::taskStatusChanged( int status )
691 : : {
692 : 0 : QgsTask *task = qobject_cast< QgsTask * >( sender() );
693 : :
694 : : //find ID of task
695 : 0 : long id = taskId( task );
696 : 0 : if ( id < 0 )
697 : 0 : return;
698 : :
699 : 0 : mTaskMutex->lock();
700 : 0 : QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
701 : 0 : mTaskMutex->unlock();
702 : 0 : if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
703 : : {
704 : 0 : delete runnable;
705 : 0 : mTasks[ id ].runnable = nullptr;
706 : 0 : }
707 : :
708 : 0 : if ( status == QgsTask::Terminated || status == QgsTask::Complete )
709 : : {
710 : 0 : bool result = status == QgsTask::Complete;
711 : 0 : task->finished( result );
712 : 0 : }
713 : :
714 : 0 : if ( status == QgsTask::Terminated )
715 : : {
716 : : //recursively cancel dependent tasks
717 : 0 : cancelDependentTasks( id );
718 : 0 : }
719 : :
720 : 0 : mTaskMutex->lock();
721 : 0 : bool isParent = mParentTasks.contains( task );
722 : 0 : mTaskMutex->unlock();
723 : 0 : if ( isParent )
724 : : {
725 : : // don't emit status changed for subtasks
726 : 0 : emit statusChanged( id, status );
727 : 0 : }
728 : :
729 : 0 : processQueue();
730 : :
731 : 0 : if ( status == QgsTask::Terminated || status == QgsTask::Complete )
732 : : {
733 : 0 : cleanupAndDeleteTask( task );
734 : 0 : }
735 : :
736 : 0 : }
737 : :
738 : 0 : void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
739 : : {
740 : 0 : mTaskMutex->lock();
741 : : // scan through layers to be removed
742 : 0 : QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
743 : 0 : layerDependencies.detach();
744 : 0 : mTaskMutex->unlock();
745 : :
746 : 0 : const auto constLayers = layers;
747 : 0 : for ( QgsMapLayer *layer : constLayers )
748 : : {
749 : : // scan through tasks with layer dependencies
750 : 0 : for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
751 : 0 : it != layerDependencies.constEnd(); ++it )
752 : : {
753 : 0 : if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
754 : : {
755 : : //task not dependent on this layer
756 : 0 : continue;
757 : : }
758 : :
759 : 0 : QgsTask *dependentTask = task( it.key() );
760 : 0 : if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
761 : : {
762 : : // incomplete task is dependent on this layer!
763 : 0 : dependentTask->cancel();
764 : 0 : }
765 : 0 : }
766 : : }
767 : 0 : }
768 : :
769 : :
770 : 0 : bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
771 : : {
772 : 0 : if ( !task )
773 : 0 : return false;
774 : :
775 : 0 : long id = taskId( task );
776 : 0 : if ( id < 0 )
777 : 0 : return false;
778 : :
779 : 0 : QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
780 : :
781 : 0 : task->disconnect( this );
782 : :
783 : 0 : mTaskMutex->lock();
784 : 0 : if ( mTaskDependencies.contains( id ) )
785 : 0 : mTaskDependencies.remove( id );
786 : 0 : mTaskMutex->unlock();
787 : :
788 : 0 : emit taskAboutToBeDeleted( id );
789 : :
790 : 0 : mTaskMutex->lock();
791 : 0 : bool isParent = mParentTasks.contains( task );
792 : 0 : mParentTasks.remove( task );
793 : 0 : mSubTasks.remove( task );
794 : 0 : mTasks.remove( id );
795 : 0 : mLayerDependencies.remove( id );
796 : :
797 : 0 : if ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated )
798 : : {
799 : 0 : if ( isParent )
800 : : {
801 : : // delete task when it's terminated
802 : 0 : connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
803 : 0 : connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
804 : 0 : }
805 : 0 : task->cancel();
806 : 0 : }
807 : : else
808 : : {
809 : 0 : if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
810 : : {
811 : 0 : delete runnable;
812 : 0 : mTasks[ id ].runnable = nullptr;
813 : 0 : }
814 : :
815 : 0 : if ( isParent )
816 : : {
817 : : //task already finished, kill it
818 : 0 : task->deleteLater();
819 : 0 : }
820 : : }
821 : :
822 : : // at this stage (hopefully) dependent tasks have been canceled or queued
823 : 0 : for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
824 : : {
825 : 0 : if ( it.value().contains( task ) )
826 : : {
827 : 0 : it.value().removeAll( task );
828 : 0 : }
829 : 0 : }
830 : 0 : mTaskMutex->unlock();
831 : :
832 : 0 : return true;
833 : 0 : }
834 : :
835 : 0 : void QgsTaskManager::processQueue()
836 : : {
837 : 0 : int prevActiveCount = countActiveTasks();
838 : 0 : mTaskMutex->lock();
839 : 0 : mActiveTasks.clear();
840 : 0 : for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
841 : : {
842 : 0 : QgsTask *task = it.value().task;
843 : 0 : if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
844 : : {
845 : 0 : it.value().createRunnable();
846 : 0 : QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
847 : 0 : }
848 : :
849 : 0 : if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
850 : : {
851 : 0 : mActiveTasks << task;
852 : 0 : }
853 : 0 : }
854 : :
855 : 0 : bool allFinished = mActiveTasks.isEmpty();
856 : 0 : mTaskMutex->unlock();
857 : :
858 : 0 : if ( allFinished )
859 : : {
860 : 0 : emit allTasksFinished();
861 : 0 : }
862 : :
863 : 0 : int newActiveCount = countActiveTasks();
864 : 0 : if ( prevActiveCount != newActiveCount )
865 : : {
866 : 0 : emit countActiveTasksChanged( newActiveCount );
867 : 0 : }
868 : 0 : }
869 : :
870 : 0 : void QgsTaskManager::cancelDependentTasks( long taskId )
871 : : {
872 : 0 : QgsTask *canceledTask = task( taskId );
873 : :
874 : : //deep copy
875 : 0 : mTaskMutex->lock();
876 : 0 : QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
877 : 0 : taskDependencies.detach();
878 : 0 : mTaskMutex->unlock();
879 : :
880 : 0 : QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
881 : 0 : for ( ; it != taskDependencies.constEnd(); ++it )
882 : : {
883 : 0 : if ( it.value().contains( canceledTask ) )
884 : : {
885 : : // found task with this dependency
886 : :
887 : : // cancel it - note that this will be recursive, so any tasks dependent
888 : : // on this one will also be canceled
889 : 0 : QgsTask *dependentTask = task( it.key() );
890 : 0 : if ( dependentTask )
891 : 0 : dependentTask->cancel();
892 : 0 : }
893 : 0 : }
894 : 0 : }
895 : :
896 : 0 : QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
897 : 0 : : task( task )
898 : 0 : , added( 0 )
899 : 0 : , priority( priority )
900 : 0 : {}
901 : :
902 : 0 : void QgsTaskManager::TaskInfo::createRunnable()
903 : : {
904 : : Q_ASSERT( !runnable );
905 : 0 : runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
906 : 0 : }
|