Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgstaskmanager.h
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 : : #ifndef QGSTASKMANAGER_H
19 : : #define QGSTASKMANAGER_H
20 : :
21 : : #include <QObject>
22 : : #include "qgis_sip.h"
23 : : #include <QMap>
24 : : #include <QFuture>
25 : : #include <QReadWriteLock>
26 : : #include <QSemaphore>
27 : : #include <QElapsedTimer>
28 : :
29 : : #include "qgis_core.h"
30 : : #include "qgsmaplayer.h"
31 : :
32 : : class QgsTask;
33 : : class QgsTaskRunnableWrapper;
34 : :
35 : : //! List of QgsTask objects
36 : : typedef QList< QgsTask * > QgsTaskList;
37 : :
38 : : /**
39 : : * \ingroup core
40 : : * \class QgsTask
41 : : * \brief Abstract base class for long running background tasks. Tasks can be controlled directly,
42 : : * or added to a QgsTaskManager for automatic management.
43 : : *
44 : : * Derived classes should implement the process they want to execute in the background
45 : : * within the run() method. This method will be called when the
46 : : * task commences (ie via calling run() ).
47 : : *
48 : : * Long running tasks should periodically check the isCanceled() flag to detect if the task
49 : : * has been canceled via some external event. If this flag is TRUE then the task should
50 : : * clean up and terminate at the earliest possible convenience.
51 : : *
52 : : * \since QGIS 3.0
53 : : */
54 : : class CORE_EXPORT QgsTask : public QObject
55 : : {
56 : : Q_OBJECT
57 : :
58 : : public:
59 : :
60 : : //! Status of tasks
61 : : enum TaskStatus
62 : : {
63 : : Queued, //!< Task is queued and has not begun
64 : : OnHold, //!< Task is queued but on hold and will not be started
65 : : Running, //!< Task is currently running
66 : : Complete, //!< Task successfully completed
67 : : Terminated, //!< Task was terminated or errored
68 : : };
69 : : Q_ENUM( TaskStatus )
70 : :
71 : : //! Task flags
72 : : enum Flag
73 : : {
74 : : CanCancel = 1 << 1, //!< Task can be canceled
75 : : CancelWithoutPrompt = 1 << 2, //!< Task can be canceled without any users prompts, e.g. when closing a project or QGIS.
76 : : AllFlags = CanCancel, //!< Task supports all flags
77 : : };
78 : : Q_DECLARE_FLAGS( Flags, Flag )
79 : :
80 : : /**
81 : : * Constructor for QgsTask.
82 : : * \param description text description of task
83 : : * \param flags task flags
84 : : */
85 : : QgsTask( const QString &description = QString(), QgsTask::Flags flags = AllFlags );
86 : :
87 : : ~QgsTask() override;
88 : :
89 : : /**
90 : : * Returns the flags associated with the task.
91 : : */
92 : : Flags flags() const { return mFlags; }
93 : :
94 : : /**
95 : : * Sets the task's \a description. This must be called before adding the task to a QgsTaskManager,
96 : : * changing the description after queuing the task has no effect.
97 : : * \since QGIS 3.10
98 : : */
99 : : void setDescription( const QString &description );
100 : :
101 : : /**
102 : : * Returns TRUE if the task can be canceled.
103 : : */
104 : 0 : bool canCancel() const { return mFlags & CanCancel; }
105 : :
106 : : /**
107 : : * Returns TRUE if the task is active, ie it is not complete and has
108 : : * not been canceled.
109 : : */
110 : : bool isActive() const { return mOverallStatus == Running; }
111 : :
112 : : /**
113 : : * Returns the current task status.
114 : : */
115 : 0 : TaskStatus status() const { return mOverallStatus; }
116 : :
117 : : /**
118 : : * Returns the task's description.
119 : : */
120 : 0 : QString description() const { return mDescription; }
121 : :
122 : : /**
123 : : * Returns the task's progress (between 0.0 and 100.0)
124 : : */
125 : 0 : double progress() const { return mTotalProgress; }
126 : :
127 : : /**
128 : : * Returns the elapsed time since the task commenced, in milliseconds.
129 : : *
130 : : * The value is undefined for tasks which have not begun.
131 : : *
132 : : * \since QGIS 3.4
133 : : */
134 : : qint64 elapsedTime() const;
135 : :
136 : : /**
137 : : * Notifies the task that it should terminate. Calling this is not guaranteed
138 : : * to immediately end the task, rather it sets the isCanceled() flag which
139 : : * task subclasses can check and terminate their operations at an appropriate
140 : : * time. Any subtasks owned by this task will also be canceled.
141 : : * Derived classes must ensure that the base class implementation is called
142 : : * from any overridden version.
143 : : * \see isCanceled()
144 : : */
145 : : virtual void cancel();
146 : :
147 : : /**
148 : : * Places the task on hold. If the task in not queued
149 : : * (ie it is already running or has finished) then calling this has no effect.
150 : : * Calling this method only has an effect for tasks which are managed
151 : : * by a QgsTaskManager.
152 : : * \see unhold()
153 : : */
154 : : void hold();
155 : :
156 : : /**
157 : : * Releases the task from being held. For tasks managed by a QgsTaskManager
158 : : * calling this will re-add them to the queue. If the
159 : : * task in not currently being held then calling this has no effect.
160 : : * \see hold()
161 : : */
162 : : void unhold();
163 : :
164 : : //! Controls how subtasks relate to their parent task
165 : : enum SubTaskDependency
166 : : {
167 : : SubTaskIndependent = 0, //!< Subtask is independent of the parent, and can run before, after or at the same time as the parent.
168 : : ParentDependsOnSubTask, //!< Subtask must complete before parent can begin
169 : : };
170 : :
171 : : /**
172 : : * Adds a subtask to this task.
173 : : *
174 : : * Subtasks allow a single task to be created which
175 : : * consists of multiple smaller tasks. Subtasks are not visible or indepedently
176 : : * controllable by users. Ownership of the subtask is transferred.
177 : : * Subtasks can have an optional list of dependent tasks, which must be completed
178 : : * before the subtask can begin. By default subtasks are considered independent
179 : : * of the parent task, ie they can be run either before, after, or at the same
180 : : * time as the parent task. This behavior can be overridden through the subTaskDependency
181 : : * argument. Note that subtasks should NEVER be dependent on their parent task, and violating
182 : : * this constraint will prevent the task from completing successfully.
183 : : *
184 : : * The parent task must be added to a QgsTaskManager for subtasks to be utilized.
185 : : * Subtasks should not be added manually to a QgsTaskManager, rather, only the parent
186 : : * task should be added to the manager.
187 : : *
188 : : * Subtasks can be nested, ie a subtask can legally be a parent task itself with
189 : : * its own set of subtasks.
190 : : */
191 : : void addSubTask( QgsTask *subTask SIP_TRANSFER, const QgsTaskList &dependencies = QgsTaskList(),
192 : : SubTaskDependency subTaskDependency = SubTaskIndependent );
193 : :
194 : : /**
195 : : * Sets a list of layers on which the task depends. The task will automatically
196 : : * be canceled if any of these layers are about to be removed.
197 : : * \see dependentLayers()
198 : : */
199 : : void setDependentLayers( const QList<QgsMapLayer *> &dependentLayers );
200 : :
201 : : /**
202 : : * Returns the list of layers on which the task depends. The task will automatically
203 : : * be canceled if any of these layers are about to be removed.
204 : : * \see setDependentLayers()
205 : : */
206 : : QList< QgsMapLayer * > dependentLayers() const;
207 : :
208 : : /**
209 : : * Blocks the current thread until the task finishes or a maximum of \a timeout milliseconds.
210 : : * If \a timeout is ``0`` the thread will be blocked forever.
211 : : * In case of a timeout, the task will still be running.
212 : : * In case the task already is finished, the method will return immediately while
213 : : * returning ``TRUE``.
214 : : *
215 : : * The result will be FALSE if the wait timed out and TRUE in any other case.
216 : : */
217 : : bool waitForFinished( int timeout = 30000 );
218 : :
219 : : signals:
220 : :
221 : : /**
222 : : * Will be emitted by task when its progress changes.
223 : : * \param progress percent of progress, from 0.0 - 100.0
224 : : * \note derived classes should not emit this signal directly, instead they should call
225 : : * setProgress()
226 : : */
227 : : void progressChanged( double progress );
228 : :
229 : : /**
230 : : * Will be emitted by task when its status changes.
231 : : * \param status new task status
232 : : * \note derived classes should not emit this signal directly, it will automatically
233 : : * be emitted
234 : : */
235 : : void statusChanged( int status );
236 : :
237 : : /**
238 : : * Will be emitted by task to indicate its commencement.
239 : : * \note derived classes should not emit this signal directly, it will automatically
240 : : * be emitted when the task begins
241 : : */
242 : : void begun();
243 : :
244 : : /**
245 : : * Will be emitted by task to indicate its successful completion.
246 : : * \note derived classes should not emit this signal directly, it will automatically
247 : : * be emitted
248 : : */
249 : : void taskCompleted();
250 : :
251 : : /**
252 : : * Will be emitted by task if it has terminated for any reason
253 : : * other then completion (e.g., when a task has been canceled or encountered
254 : : * an internal error).
255 : : * \note derived classes should not emit this signal directly, it will automatically
256 : : * be emitted
257 : : */
258 : : void taskTerminated();
259 : :
260 : : protected:
261 : :
262 : : /**
263 : : * Performs the task's operation. This method will be called when the task commences
264 : : * (ie via calling start() ), and subclasses should implement the operation they
265 : : * wish to perform in the background within this method.
266 : : *
267 : : * A task must return a boolean value to indicate whether the
268 : : * task was completed successfully or terminated before completion.
269 : : */
270 : : virtual bool run() = 0;
271 : :
272 : : /**
273 : : * If the task is managed by a QgsTaskManager, this will be called after the
274 : : * task has finished (whether through successful completion or via early
275 : : * termination). The result argument reflects whether
276 : : * the task was successfully completed or not. This method is always called
277 : : * from the main thread, so it is safe to create widgets and perform other
278 : : * operations which require the main thread. However, the GUI will be blocked
279 : : * for the duration of this method so tasks should avoid performing any
280 : : * lengthy operations here.
281 : : */
282 : : virtual void finished( bool result ) { Q_UNUSED( result ) }
283 : :
284 : : /**
285 : : * Will return TRUE if task should terminate ASAP. If the task reports the CanCancel
286 : : * flag, then derived classes' run() methods should periodically check this and
287 : : * terminate in a safe manner.
288 : : */
289 : : bool isCanceled() const;
290 : :
291 : : protected slots:
292 : :
293 : : /**
294 : : * Sets the task's current progress. The derived class should call this method whenever
295 : : * the task wants to update its progress. Calling will automatically emit the progressChanged signal.
296 : : * \param progress percent of progress, from 0.0 - 100.0
297 : : */
298 : : void setProgress( double progress );
299 : :
300 : : private slots:
301 : : void subTaskStatusChanged( int status );
302 : :
303 : : private:
304 : :
305 : : Flags mFlags;
306 : : QString mDescription;
307 : : //! Status of this (parent) task alone
308 : : TaskStatus mStatus = Queued;
309 : : //! Status of this task and all subtasks
310 : : TaskStatus mOverallStatus = Queued;
311 : :
312 : : /**
313 : : * This mutex remains locked from initialization until the task finishes,
314 : : * it's used as a trigger for waitForFinished.
315 : : */
316 : : QMutex mNotFinishedMutex;
317 : :
318 : : /**
319 : : * This semaphore remains locked from task creation until the task actually start,
320 : : * it's used in waitForFinished to actually wait the task to be started.
321 : : */
322 : : QSemaphore mNotStartedMutex;
323 : :
324 : : //! Progress of this (parent) task alone
325 : : double mProgress = 0.0;
326 : : //! Overall progress of this task and all subtasks
327 : : double mTotalProgress = 0.0;
328 : : bool mShouldTerminate = false;
329 : : mutable QMutex mShouldTerminateMutex;
330 : : int mStartCount = 0;
331 : :
332 : 0 : struct SubTask
333 : : {
334 : 0 : SubTask( QgsTask *task, const QgsTaskList &dependencies, SubTaskDependency dependency )
335 : 0 : : task( task )
336 : 0 : , dependencies( dependencies )
337 : 0 : , dependency( dependency )
338 : 0 : {}
339 : : QgsTask *task = nullptr;
340 : : QgsTaskList dependencies;
341 : : SubTaskDependency dependency;
342 : : };
343 : : QList< SubTask > mSubTasks;
344 : :
345 : : QgsWeakMapLayerPointerList mDependentLayers;
346 : :
347 : : QElapsedTimer mElapsedTime;
348 : :
349 : :
350 : : /**
351 : : * Starts the task. Should not be public as only QgsTaskManagers can initiate tasks.
352 : : */
353 : : void start();
354 : :
355 : : /**
356 : : * Called when the task has completed successfully.
357 : : */
358 : : void completed();
359 : :
360 : : /**
361 : : * Called when the task has failed, as either a result of an internal failure or via cancellation.
362 : : */
363 : : void terminated();
364 : :
365 : :
366 : : void processSubTasksForHold();
367 : :
368 : : friend class QgsTaskManager;
369 : : friend class QgsTaskRunnableWrapper;
370 : : friend class TestQgsTaskManager;
371 : :
372 : : private slots:
373 : :
374 : : void processSubTasksForCompletion();
375 : :
376 : : void processSubTasksForTermination();
377 : :
378 : : };
379 : :
380 : :
381 : 0 : Q_DECLARE_OPERATORS_FOR_FLAGS( QgsTask::Flags )
382 : :
383 : : /**
384 : : * \ingroup core
385 : : * \class QgsTaskManager
386 : : * \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
387 : : * or accessed via QgsApplication::taskManager().
388 : : * \since QGIS 3.0
389 : : */
390 : : class CORE_EXPORT QgsTaskManager : public QObject
391 : : {
392 : : Q_OBJECT
393 : :
394 : : public:
395 : :
396 : : /**
397 : : * Constructor for QgsTaskManager.
398 : : * \param parent parent QObject
399 : : */
400 : : QgsTaskManager( QObject *parent SIP_TRANSFERTHIS = nullptr );
401 : :
402 : : ~QgsTaskManager() override;
403 : :
404 : : /**
405 : : * Definition of a task for inclusion in the manager.
406 : : */
407 : : struct TaskDefinition
408 : : {
409 : :
410 : : /**
411 : : * Constructor for TaskDefinition. Ownership of the task is not transferred to the definition,
412 : : * but will be transferred to a QgsTaskManager.
413 : : */
414 : : explicit TaskDefinition( QgsTask *task, const QgsTaskList &dependentTasks = QgsTaskList() )
415 : : : task( task )
416 : : , dependentTasks( dependentTasks )
417 : : {}
418 : :
419 : : //! Task
420 : : QgsTask *task = nullptr;
421 : :
422 : : /**
423 : : * List of dependent tasks which must be completed before task can run. If any dependent tasks are
424 : : * canceled this task will also be canceled. Dependent tasks must also be added
425 : : * to the task manager for proper handling of dependencies.
426 : : */
427 : : QgsTaskList dependentTasks;
428 : :
429 : : };
430 : :
431 : : /**
432 : : * Adds a task to the manager. Ownership of the task is transferred
433 : : * to the manager, and the task manager will be responsible for starting
434 : : * the task. The priority argument can be used to control the run queue's
435 : : * order of execution, with larger numbers
436 : : * taking precedence over lower priority numbers.
437 : : * \returns unique task ID, or 0 if task could not be added
438 : : */
439 : : long addTask( QgsTask *task SIP_TRANSFER, int priority = 0 );
440 : :
441 : : /**
442 : : * Adds a task to the manager, using a full task definition (including dependency
443 : : * handling). Ownership of the task is transferred to the manager, and the task
444 : : * manager will be responsible for starting the task. The priority argument can
445 : : * be used to control the run queue's order of execution, with larger numbers
446 : : * taking precedence over lower priority numbers.
447 : : * \returns unique task ID, or 0 if task could not be added
448 : : */
449 : : long addTask( const TaskDefinition &task SIP_TRANSFER, int priority = 0 );
450 : :
451 : : /**
452 : : * Returns the task with matching ID.
453 : : * \param id task ID
454 : : * \returns task if found, or NULLPTR
455 : : */
456 : : QgsTask *task( long id ) const;
457 : :
458 : : /**
459 : : * Returns all tasks tracked by the manager.
460 : : */
461 : : QList<QgsTask *> tasks() const;
462 : :
463 : : //! Returns the number of tasks tracked by the manager.
464 : : int count() const;
465 : :
466 : : /**
467 : : * Returns the unique task ID corresponding to a task managed by the class.
468 : : * \param task task to find
469 : : * \returns task ID, or -1 if task not found
470 : : */
471 : : long taskId( QgsTask *task ) const;
472 : :
473 : : /**
474 : : * Instructs all tasks tracked by the manager to terminate. Individual tasks may take some time
475 : : * to cancel, or may totally ignore this instruction. Calling this does not block
476 : : * but will instead signal the tasks to cancel and then return immediately.
477 : : */
478 : : void cancelAll();
479 : :
480 : : //! Returns TRUE if all dependencies for the specified task are satisfied
481 : : bool dependenciesSatisfied( long taskId ) const;
482 : :
483 : : /**
484 : : * Returns the set of task IDs on which a task is dependent
485 : : * \note not available in Python bindings
486 : : */
487 : : QSet< long > dependencies( long taskId ) const SIP_SKIP;
488 : :
489 : : /**
490 : : * Returns a list of layers on which as task is dependent. The task will automatically
491 : : * be canceled if any of these layers are about to be removed.
492 : : * \param taskId task ID
493 : : * \returns list of layers
494 : : * \see tasksDependentOnLayer()
495 : : */
496 : : QList< QgsMapLayer * > dependentLayers( long taskId ) const;
497 : :
498 : : /**
499 : : * Returns a list of tasks which depend on a layer.
500 : : * \see dependentLayers()
501 : : */
502 : : QList< QgsTask * > tasksDependentOnLayer( QgsMapLayer *layer ) const;
503 : :
504 : : /**
505 : : * Returns a list of the active (queued or running) tasks.
506 : : * \see countActiveTasks()
507 : : */
508 : : QList< QgsTask * > activeTasks() const;
509 : :
510 : : /**
511 : : * Returns the number of active (queued or running) tasks.
512 : : * \see activeTasks()
513 : : * \see countActiveTasksChanged()
514 : : */
515 : : int countActiveTasks() const;
516 : :
517 : : public slots:
518 : :
519 : : /**
520 : : * Triggers a task, e.g. as a result of a GUI interaction.
521 : : * \see taskTriggered()
522 : : */
523 : : void triggerTask( QgsTask *task );
524 : :
525 : : signals:
526 : :
527 : : /**
528 : : * Will be emitted when a task reports a progress change
529 : : * \param taskId ID of task
530 : : * \param progress percent of progress, from 0.0 - 100.0
531 : : */
532 : : void progressChanged( long taskId, double progress );
533 : :
534 : : /**
535 : : * Will be emitted when only a single task remains to complete
536 : : * and that task has reported a progress change
537 : : * \param progress percent of progress, from 0.0 - 100.0
538 : : */
539 : : void finalTaskProgressChanged( double progress );
540 : :
541 : : /**
542 : : * Will be emitted when a task reports a status change
543 : : * \param taskId ID of task
544 : : * \param status new task status
545 : : */
546 : : void statusChanged( long taskId, int status );
547 : :
548 : : /**
549 : : * Emitted when a new task has been added to the manager
550 : : * \param taskId ID of task
551 : : */
552 : : void taskAdded( long taskId );
553 : :
554 : : /**
555 : : * Emitted when a task is about to be deleted
556 : : * \param taskId ID of task
557 : : */
558 : : void taskAboutToBeDeleted( long taskId );
559 : :
560 : : /**
561 : : * Emitted when all tasks are complete
562 : : * \see countActiveTasksChanged()
563 : : */
564 : : void allTasksFinished();
565 : :
566 : : /**
567 : : * Emitted when the number of active tasks changes
568 : : * \see countActiveTasks()
569 : : */
570 : : void countActiveTasksChanged( int count );
571 : :
572 : : /**
573 : : * Emitted when a \a task is triggered. This occurs when a user clicks on
574 : : * the task from the QGIS GUI, and can be used to show detailed progress
575 : : * reports or re-open a related dialog.
576 : : * \see triggerTask()
577 : : */
578 : : void taskTriggered( QgsTask *task );
579 : :
580 : : private slots:
581 : :
582 : : void taskProgressChanged( double progress );
583 : : void taskStatusChanged( int status );
584 : : void layersWillBeRemoved( const QList<QgsMapLayer *> &layers );
585 : :
586 : : private:
587 : :
588 : 0 : struct TaskInfo
589 : : {
590 : : TaskInfo( QgsTask *task = nullptr, int priority = 0 );
591 : : void createRunnable();
592 : : QgsTask *task = nullptr;
593 : : QAtomicInt added;
594 : : int priority;
595 : : QgsTaskRunnableWrapper *runnable = nullptr;
596 : : };
597 : :
598 : : bool mInitialized = false;
599 : :
600 : : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
601 : : mutable QMutex *mTaskMutex;
602 : : #else
603 : : mutable QRecursiveMutex *mTaskMutex;
604 : : #endif
605 : :
606 : : QMap< long, TaskInfo > mTasks;
607 : : QMap< long, QgsTaskList > mTaskDependencies;
608 : : QMap< long, QgsWeakMapLayerPointerList > mLayerDependencies;
609 : :
610 : : //! Tracks the next unique task ID
611 : : long mNextTaskId = 1;
612 : :
613 : : //! List of active (queued or running) tasks. Includes subtasks.
614 : : QSet< QgsTask * > mActiveTasks;
615 : : //! List of parent tasks
616 : : QSet< QgsTask * > mParentTasks;
617 : : //! List of subtasks
618 : : QSet< QgsTask * > mSubTasks;
619 : :
620 : : QSet< QgsTask * > mPendingDeletion;
621 : :
622 : : long addTaskPrivate( QgsTask *task,
623 : : QgsTaskList dependencies,
624 : : bool isSubTask,
625 : : int priority );
626 : :
627 : : bool cleanupAndDeleteTask( QgsTask *task );
628 : :
629 : : /**
630 : : * Process the queue of outstanding jobs and starts up any
631 : : * which are ready to go.
632 : : */
633 : : void processQueue();
634 : :
635 : : /**
636 : : * Recursively cancel dependent tasks
637 : : * \param taskId id of terminated task to cancel any other tasks
638 : : * which are dependent on
639 : : */
640 : : void cancelDependentTasks( long taskId );
641 : :
642 : : bool resolveDependencies( long firstTaskId, long currentTaskId, QSet< long > &results ) const;
643 : :
644 : : //! Will return TRUE if the specified task has circular dependencies
645 : : bool hasCircularDependencies( long taskId ) const;
646 : :
647 : : friend class TestQgsTaskManager;
648 : : };
649 : :
650 : : #endif //QGSTASKMANAGER_H
|