• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--- TEST_NAME ComposingGuideTest -->
2
3[//]: # (title: Composing suspending functions)
4
5This section covers various approaches to composition of suspending functions.
6
7## Sequential by default
8
9Assume that we have two suspending functions defined elsewhere that do something useful like some kind of
10remote service call or computation. We just pretend they are useful, but actually each one just
11delays for a second for the purpose of this example:
12
13```kotlin
14suspend fun doSomethingUsefulOne(): Int {
15    delay(1000L) // pretend we are doing something useful here
16    return 13
17}
18
19suspend fun doSomethingUsefulTwo(): Int {
20    delay(1000L) // pretend we are doing something useful here, too
21    return 29
22}
23```
24
25What do we do if we need them to be invoked _sequentially_ &mdash; first `doSomethingUsefulOne` _and then_
26`doSomethingUsefulTwo`, and compute the sum of their results?
27In practice, we do this if we use the result of the first function to make a decision on whether we need
28to invoke the second one or to decide on how to invoke it.
29
30We use a normal sequential invocation, because the code in the coroutine, just like in the regular
31code, is _sequential_ by default. The following example demonstrates it by measuring the total
32time it takes to execute both suspending functions:
33
34<!--- CLEAR -->
35
36```kotlin
37import kotlinx.coroutines.*
38import kotlin.system.*
39
40fun main() = runBlocking<Unit> {
41//sampleStart
42    val time = measureTimeMillis {
43        val one = doSomethingUsefulOne()
44        val two = doSomethingUsefulTwo()
45        println("The answer is ${one + two}")
46    }
47    println("Completed in $time ms")
48//sampleEnd
49}
50
51suspend fun doSomethingUsefulOne(): Int {
52    delay(1000L) // pretend we are doing something useful here
53    return 13
54}
55
56suspend fun doSomethingUsefulTwo(): Int {
57    delay(1000L) // pretend we are doing something useful here, too
58    return 29
59}
60```
61{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
62
63> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt).
64>
65{type="note"}
66
67It produces something like this:
68
69```text
70The answer is 42
71Completed in 2017 ms
72```
73
74<!--- TEST ARBITRARY_TIME -->
75
76## Concurrent using async
77
78What if there are no dependencies between invocations of `doSomethingUsefulOne` and `doSomethingUsefulTwo` and
79we want to get the answer faster, by doing both _concurrently_? This is where [async] comes to help.
80
81Conceptually, [async] is just like [launch]. It starts a separate coroutine which is a light-weight thread
82that works concurrently with all the other coroutines. The difference is that `launch` returns a [Job] and
83does not carry any resulting value, while `async` returns a [Deferred] &mdash; a light-weight non-blocking future
84that represents a promise to provide a result later. You can use `.await()` on a deferred value to get its eventual result,
85but `Deferred` is also a `Job`, so you can cancel it if needed.
86
87```kotlin
88import kotlinx.coroutines.*
89import kotlin.system.*
90
91fun main() = runBlocking<Unit> {
92//sampleStart
93    val time = measureTimeMillis {
94        val one = async { doSomethingUsefulOne() }
95        val two = async { doSomethingUsefulTwo() }
96        println("The answer is ${one.await() + two.await()}")
97    }
98    println("Completed in $time ms")
99//sampleEnd
100}
101
102suspend fun doSomethingUsefulOne(): Int {
103    delay(1000L) // pretend we are doing something useful here
104    return 13
105}
106
107suspend fun doSomethingUsefulTwo(): Int {
108    delay(1000L) // pretend we are doing something useful here, too
109    return 29
110}
111```
112{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
113
114> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt).
115>
116{type="note"}
117
118It produces something like this:
119
120```text
121The answer is 42
122Completed in 1017 ms
123```
124
125<!--- TEST ARBITRARY_TIME -->
126
127This is twice as fast, because the two coroutines execute concurrently.
128Note that concurrency with coroutines is always explicit.
129
130## Lazily started async
131
132Optionally, [async] can be made lazy by setting its `start` parameter to [CoroutineStart.LAZY].
133In this mode it only starts the coroutine when its result is required by
134[await][Deferred.await], or if its `Job`'s [start][Job.start] function
135is invoked. Run the following example:
136
137```kotlin
138import kotlinx.coroutines.*
139import kotlin.system.*
140
141fun main() = runBlocking<Unit> {
142//sampleStart
143    val time = measureTimeMillis {
144        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
145        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
146        // some computation
147        one.start() // start the first one
148        two.start() // start the second one
149        println("The answer is ${one.await() + two.await()}")
150    }
151    println("Completed in $time ms")
152//sampleEnd
153}
154
155suspend fun doSomethingUsefulOne(): Int {
156    delay(1000L) // pretend we are doing something useful here
157    return 13
158}
159
160suspend fun doSomethingUsefulTwo(): Int {
161    delay(1000L) // pretend we are doing something useful here, too
162    return 29
163}
164```
165{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
166
167> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt).
168>
169{type="note"}
170
171It produces something like this:
172
173```text
174The answer is 42
175Completed in 1017 ms
176```
177
178<!--- TEST ARBITRARY_TIME -->
179
180So, here the two coroutines are defined but not executed as in the previous example, but the control is given to
181the programmer on when exactly to start the execution by calling [start][Job.start]. We first
182start `one`, then start `two`, and then await for the individual coroutines to finish.
183
184Note that if we just call [await][Deferred.await] in `println` without first calling [start][Job.start] on individual
185coroutines, this will lead to sequential behavior, since [await][Deferred.await] starts the coroutine
186execution and waits for its finish, which is not the intended use-case for laziness.
187The use-case for `async(start = CoroutineStart.LAZY)` is a replacement for the
188standard `lazy` function in cases when computation of the value involves suspending functions.
189
190## Async-style functions
191
192We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
193_asynchronously_ using the [async] coroutine builder using a [GlobalScope] reference to
194opt-out of the structured concurrency.
195We name such functions with the
196"...Async" suffix to highlight the fact that they only start asynchronous computation and one needs
197to use the resulting deferred value to get the result.
198
199> [GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained
200> below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
201>
202{type="note"}
203
204```kotlin
205// The result type of somethingUsefulOneAsync is Deferred<Int>
206@OptIn(DelicateCoroutinesApi::class)
207fun somethingUsefulOneAsync() = GlobalScope.async {
208    doSomethingUsefulOne()
209}
210
211// The result type of somethingUsefulTwoAsync is Deferred<Int>
212@OptIn(DelicateCoroutinesApi::class)
213fun somethingUsefulTwoAsync() = GlobalScope.async {
214    doSomethingUsefulTwo()
215}
216```
217
218Note that these `xxxAsync` functions are **not** _suspending_ functions. They can be used from anywhere.
219However, their use always implies asynchronous (here meaning _concurrent_) execution of their action
220with the invoking code.
221
222The following example shows their use outside of coroutine:
223
224<!--- CLEAR -->
225
226```kotlin
227import kotlinx.coroutines.*
228import kotlin.system.*
229
230//sampleStart
231// note that we don't have `runBlocking` to the right of `main` in this example
232fun main() {
233    val time = measureTimeMillis {
234        // we can initiate async actions outside of a coroutine
235        val one = somethingUsefulOneAsync()
236        val two = somethingUsefulTwoAsync()
237        // but waiting for a result must involve either suspending or blocking.
238        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
239        runBlocking {
240            println("The answer is ${one.await() + two.await()}")
241        }
242    }
243    println("Completed in $time ms")
244}
245//sampleEnd
246
247@OptIn(DelicateCoroutinesApi::class)
248fun somethingUsefulOneAsync() = GlobalScope.async {
249    doSomethingUsefulOne()
250}
251
252@OptIn(DelicateCoroutinesApi::class)
253fun somethingUsefulTwoAsync() = GlobalScope.async {
254    doSomethingUsefulTwo()
255}
256
257suspend fun doSomethingUsefulOne(): Int {
258    delay(1000L) // pretend we are doing something useful here
259    return 13
260}
261
262suspend fun doSomethingUsefulTwo(): Int {
263    delay(1000L) // pretend we are doing something useful here, too
264    return 29
265}
266```
267{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
268
269> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt).
270>
271{type="note"}
272
273<!--- TEST ARBITRARY_TIME
274The answer is 42
275Completed in 1085 ms
276-->
277
278> This programming style with async functions is provided here only for illustration, because it is a popular style
279> in other programming languages. Using this style with Kotlin coroutines is **strongly discouraged** for the
280> reasons explained below.
281>
282{type="note"}
283
284Consider what happens if between the `val one = somethingUsefulOneAsync()` line and `one.await()` expression there is some logic
285error in the code, and the program throws an exception, and the operation that was being performed by the program aborts.
286Normally, a global error-handler could catch this exception, log and report the error for developers, but the program
287could otherwise continue doing other operations. However, here we have `somethingUsefulOneAsync` still running in the background,
288even though the operation that initiated it was aborted. This problem does not happen with structured
289concurrency, as shown in the section below.
290
291## Structured concurrency with async
292
293Let us take the [Concurrent using async](#concurrent-using-async) example and extract a function that
294concurrently performs `doSomethingUsefulOne` and `doSomethingUsefulTwo` and returns the sum of their results.
295Because the [async] coroutine builder is defined as an extension on [CoroutineScope], we need to have it in the
296scope and that is what the [coroutineScope][_coroutineScope] function provides:
297
298```kotlin
299suspend fun concurrentSum(): Int = coroutineScope {
300    val one = async { doSomethingUsefulOne() }
301    val two = async { doSomethingUsefulTwo() }
302    one.await() + two.await()
303}
304```
305
306This way, if something goes wrong inside the code of the `concurrentSum` function, and it throws an exception,
307all the coroutines that were launched in its scope will be cancelled.
308
309<!--- CLEAR -->
310
311```kotlin
312import kotlinx.coroutines.*
313import kotlin.system.*
314
315fun main() = runBlocking<Unit> {
316//sampleStart
317    val time = measureTimeMillis {
318        println("The answer is ${concurrentSum()}")
319    }
320    println("Completed in $time ms")
321//sampleEnd
322}
323
324suspend fun concurrentSum(): Int = coroutineScope {
325    val one = async { doSomethingUsefulOne() }
326    val two = async { doSomethingUsefulTwo() }
327    one.await() + two.await()
328}
329
330suspend fun doSomethingUsefulOne(): Int {
331    delay(1000L) // pretend we are doing something useful here
332    return 13
333}
334
335suspend fun doSomethingUsefulTwo(): Int {
336    delay(1000L) // pretend we are doing something useful here, too
337    return 29
338}
339```
340{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
341
342> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt).
343>
344{type="note"}
345
346We still have concurrent execution of both operations, as evident from the output of the above `main` function:
347
348```text
349The answer is 42
350Completed in 1017 ms
351```
352
353<!--- TEST ARBITRARY_TIME -->
354
355Cancellation is always propagated through coroutines hierarchy:
356
357<!--- CLEAR -->
358
359```kotlin
360import kotlinx.coroutines.*
361
362fun main() = runBlocking<Unit> {
363    try {
364        failedConcurrentSum()
365    } catch(e: ArithmeticException) {
366        println("Computation failed with ArithmeticException")
367    }
368}
369
370suspend fun failedConcurrentSum(): Int = coroutineScope {
371    val one = async<Int> {
372        try {
373            delay(Long.MAX_VALUE) // Emulates very long computation
374            42
375        } finally {
376            println("First child was cancelled")
377        }
378    }
379    val two = async<Int> {
380        println("Second child throws an exception")
381        throw ArithmeticException()
382    }
383    one.await() + two.await()
384}
385```
386{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
387
388> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt).
389>
390{type="note"}
391
392Note how both the first `async` and the awaiting parent are cancelled on failure of one of the children
393(namely, `two`):
394```text
395Second child throws an exception
396First child was cancelled
397Computation failed with ArithmeticException
398```
399
400<!--- TEST -->
401
402<!--- MODULE kotlinx-coroutines-core -->
403<!--- INDEX kotlinx.coroutines -->
404
405[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
406[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
407[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
408[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
409[CoroutineStart.LAZY]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y/index.html
410[Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
411[Job.start]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
412[GlobalScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
413[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
414[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
415
416<!--- END -->
417