diff options
Diffstat (limited to 'examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc')
-rw-r--r-- | examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc | 206 |
1 files changed, 175 insertions, 31 deletions
diff --git a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc index 0134f0f8e8..499cb165c8 100644 --- a/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc +++ b/examples/qtconcurrent/imagescaling/doc/src/qtconcurrent-imagescaling.qdoc @@ -1,37 +1,181 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:FDL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! \example imagescaling - \title Image Scaling Example - \brief Demonstrates how to asynchronously scale images. + \meta tags {widgets, threads, network} + \title Image Scaling \ingroup qtconcurrentexamples - \image imagescaling_example.png + \examplecategory {Networking} + \brief Demonstrates how to asynchronously download and scale images. - The QtConcurrent Map example shows how to use the asynchronous - QtConcurrent API to load and scale a collection of images. + This example shows how to use the QFuture, QPromise, and QFutureWatcher + classes to download a collection of images from the network and scale them, + without blocking the UI. + + \image imagescaling.webp + + The application consists of the following steps: + + \list 1 + \li Download images form the list of URLs specified by the user. + \li Scale the images. + \li Show the scaled images in a grid layout. + \endlist + + Let's start with the download: + + \snippet imagescaling/imagescaling.cpp 8 + + The \c download() method takes a list of URLs and returns a QFuture. The QFuture + stores the byte array data received for each downloaded image. To store the data + inside the QFuture, we create a QPromise object and report that it has started to + indicate the start of the download: + + \snippet imagescaling/imagescaling.cpp 9 + \dots + \snippet imagescaling/imagescaling.cpp 13 + + The future associated with the promise is returned to the caller. + + Without going into details yet, let's note that the promise object is wrapped + inside a QSharedPointer. This will be explained later. + + We use QNetworkAccessManager to send network requests and download data for each + url: + + \snippet imagescaling/imagescaling.cpp 10 + + And here starts the interesting part: + + \snippet imagescaling/imagescaling.cpp 11 + \dots + + Instead of connecting to QNetworkReply's signals using the QObject::connect() + method, we use QtFuture::connect(). It works similar to QObject::connect(), but + returns a QFuture object, that becomes available as soon as the + QNetworkReply::finished() signal is emitted. This allows us to attach continuations + and failure handlers, as it is done in the example. + + In the continuation attached via \l{QFuture::then}{.then()}, we check if the + user has requested to cancel the download. If that's the case, we stop + processing the request. By calling the \l QPromise::finish() method, we notify + the user that processing has been finished. + In case the network request has ended with an error, we throw an exception. The + exception will be handled in the failure handler attached using the + \l{QFuture::onFailed}{.onFailed()} method. + Note that we have two failure handlers: the first one captures the network + errors, the second one all other exceptions thrown during the execution. Both handlers + save the exception inside the promise object (to be handled by the caller of the + \c download() method) and report that the computation has finished. Also note that, + for simplicity, in case of an error we interrupt all pending downloads. + + If the request has not been canceled and no error occurred, we read the data from + the network reply and add it to the list of results of the promise object: + + \dots + \snippet imagescaling/imagescaling.cpp 12 + \dots + + If the number of results stored inside the promise object is equal to the number + of the \c {url}s to be downloaded, there are no more requests to process, so we also + report that the promise has finished. + + As mentioned earlier, we've wrapped the promise inside a QSharedPointer. + Since the promise object is shared between handlers connected to each network reply, + we need to copy and use the promise object in multiple places simultaneously. Hence, + a QSharedPointer is used. + + The \c download() method is called from the \c Images::process method. It is invoked + when the user presses the \e {"Add URLs"} button: + + \dots + \snippet imagescaling/imagescaling.cpp 1 + \dots + + After clearing the possible leftovers from previous download, we create a dialog + so that the user can specify the URLs for the images to download. Based on the + specified URL count, we initialize the layout where the images will be shown and + start the download. The future returned by the \c download() method is saved, so that + the user can cancel the download if needed: + + \snippet imagescaling/imagescaling.cpp 3 + \dots + + Next, we attach a continuation to handle the scaling step. + More on that later: + + \snippet imagescaling/imagescaling.cpp 4 + \dots + + After that we attach \l {QFuture::}{onCanceled()} and \l {QFuture::}{onFailed()} + handlers: + + \snippet imagescaling/imagescaling.cpp 5 + \dots + + The handler attached via the \l {QFuture::onCanceled}{.onCanceled()} method + will be called if the user has pressed the \e "Cancel" button: + + \dots + \snippet imagescaling/imagescaling.cpp 2 + \dots + + The \c cancel() method simply aborts all the pending requests: + + \snippet imagescaling/imagescaling.cpp 7 + + The handlers attached via \l {QFuture::onFailed}{.onFailed()} method will be + called in case an error occurred during one of the previous steps. + For example, if a network error has been saved inside the promise during the + download step, it will be propagated to the handler that takes + \l QNetworkReply::NetworkError as argument. + + If the \c downloadFuture is not canceled, and didn't report any error, the + scaling continuation is executed. + + Since the scaling may be computationally heavy, and we don't want to block + the main thread, we use \l QtConcurrent::run(), to launch the scaling step + in a new thread. + + \snippet imagescaling/imagescaling.cpp 16 + + Since the scaling is launched in a separate thread, the user can potentially + decide to close the application while the scaling operation is in progress. + To handle such situations gracefully, we pass the \l QFuture returned by + \l QtConcurrent::run() to the \l QFutureWatcher instance. + + The watcher's \l QFutureWatcher::finished signal is connected to the + \c Images::scaleFinished slot: + + \snippet imagescaling/imagescaling.cpp 6 + + This slot is responsible for showing the scaled images in the UI, and also + for handling the errors that could potentially happen during scaling: + + \snippet imagescaling/imagescaling.cpp 15 + + The error reporting is implemented by returning an optional from the + \c Images::scaled() method: + + \snippet imagescaling/imagescaling.cpp 14 + + The \c Images::OptionalImages type here is simply a typedef for \c std::optional: + + \snippet imagescaling/imagescaling.h 1 + + \note We cannot handle the errors from the async scaling operation using + the \l {QFuture::onFailed}{.onFailed()} handler, because the handler needs + to be executed in the context of \c Images object in the UI thread. + If the user closes the application while the async computation is done, + the \c Images object will be destroyed, and accessing its members from the + continuation will lead to a crash. Using \l QFutureWatcher and its signals + allows us to avoid the problem, because the signals are disconnected when + the \l QFutureWatcher is destroyed, so the related slots will never be + executed in a destroyed context. + + The rest of the code is straightforward, you can check the example project for + more details. + + \include examples-run.qdocinc */ |