mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-04-19 16:17:38 +08:00
182 lines
7.3 KiB
Plaintext
182 lines
7.3 KiB
Plaintext
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
|
|
|
/*!
|
|
\example imagescaling
|
|
\meta tags {widgets, threads, network}
|
|
\title Image Scaling
|
|
\ingroup qtconcurrentexamples
|
|
\examplecategory {Networking}
|
|
\brief Demonstrates how to asynchronously download and scale 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
|
|
*/
|