1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
// Copyright (C) 2020 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 download and scale images.
\ingroup qtconcurrentexamples
\image imagescaling_example.png
This example shows how to use the QFuture and QPromise classes to download a
collection of images from the network and scale them, without blocking the UI.
The application consists of the 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:
\dots
\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 \b{.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 \c 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 \b{.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.
\c download() method is called from the \c QImage::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:
\snippet imagescaling/imagescaling.cpp 4
\dots
Since the scaling may be computationally heavy, and we don't want to block the main
thread, we pass the \c QtFuture::Launch::Async option, to launch the scaling step in
a new thread. The \c scaled() method returns a list of the scaled images to the next
step, which takes care of showing images in the layout.
Note that \c updateStatus() is called through QMetaObject::invokeMethod(),
because it updates the UI and needs to be invoked from the main thread.
\dots
\snippet imagescaling/imagescaling.cpp 5
\dots
For the same reason \c showImages() also needs to be invoked from the main thread, so
we pass \c this as a context to \c .then(). By default, \c .then() is launched in the
parent's thread, but if a context object is specified, it is launched in the context
object's thread.
Then we add cancellation and failure handlers:
\dots
\snippet imagescaling/imagescaling.cpp 6
We don't need to specify the context anymore, because \c .onCanceled() and the next
handlers will be launched in their parent's context.
The handler attached via the \c .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 \c .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 \c QNetworkReply::NetworkError as argument. A failure can
happen also during the scaling step:
\snippet imagescaling/imagescaling.cpp 14
The rest of the code is straightforward, you can check the example project for
more details.
*/
|