|
10 | 10 | * [Suspending functions](#suspending-functions)
|
11 | 11 | * [Flows](#flows)
|
12 | 12 | * [Flows are cold](#flows-are-cold)
|
13 |
| - * [Flow cancellation](#flow-cancellation) |
| 13 | + * [Flow cancellation basics](#flow-cancellation-basics) |
14 | 14 | * [Flow builders](#flow-builders)
|
15 | 15 | * [Intermediate flow operators](#intermediate-flow-operators)
|
16 | 16 | * [Transform operator](#transform-operator)
|
|
42 | 42 | * [Successful completion](#successful-completion)
|
43 | 43 | * [Imperative versus declarative](#imperative-versus-declarative)
|
44 | 44 | * [Launching flow](#launching-flow)
|
| 45 | + * [Flow cancellation checks](#flow-cancellation-checks) |
| 46 | + * [Making busy flow cancellable](#making-busy-flow-cancellable) |
45 | 47 | * [Flow and Reactive Streams](#flow-and-reactive-streams)
|
46 | 48 |
|
47 | 49 | <!--- END -->
|
@@ -267,12 +269,10 @@ This is a key reason the `foo()` function (which returns a flow) is not marked w
|
267 | 269 | By itself, `foo()` returns quickly and does not wait for anything. The flow starts every time it is collected,
|
268 | 270 | that is why we see "Flow started" when we call `collect` again.
|
269 | 271 |
|
270 |
| -### Flow cancellation |
271 |
| - |
272 |
| -Flow adheres to the general cooperative cancellation of coroutines. However, flow infrastructure does not introduce |
273 |
| -additional cancellation points. It is fully transparent for cancellation. As usual, flow collection can be |
274 |
| -cancelled when the flow is suspended in a cancellable suspending function (like [delay]), and cannot be cancelled otherwise. |
| 272 | +### Flow cancellation basics |
275 | 273 |
|
| 274 | +Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be |
| 275 | +cancelled when the flow is suspended in a cancellable suspending function (like [delay]). |
276 | 276 | The following example shows how the flow gets cancelled on a timeout when running in a [withTimeoutOrNull] block
|
277 | 277 | and stops executing its code:
|
278 | 278 |
|
|
316 | 316 |
|
317 | 317 | <!--- TEST -->
|
318 | 318 |
|
| 319 | +See [Flow cancellation checks](#flow-cancellation-checks) section for more details. |
| 320 | + |
319 | 321 | ### Flow builders
|
320 | 322 |
|
321 | 323 | The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for
|
@@ -1777,6 +1779,127 @@ as cancellation and structured concurrency serve this purpose.
|
1777 | 1779 | Note that [launchIn] also returns a [Job], which can be used to [cancel][Job.cancel] the corresponding flow collection
|
1778 | 1780 | coroutine only without cancelling the whole scope or to [join][Job.join] it.
|
1779 | 1781 |
|
| 1782 | +### Flow cancellation checks |
| 1783 | + |
| 1784 | +For convenience, the [flow] builder performs additional [ensureActive] checks for cancellation on each emitted value. |
| 1785 | +It means that a busy loop emitting from a `flow { ... }` is cancellable: |
| 1786 | + |
| 1787 | +<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
| 1788 | + |
| 1789 | +```kotlin |
| 1790 | +import kotlinx.coroutines.* |
| 1791 | +import kotlinx.coroutines.flow.* |
| 1792 | + |
| 1793 | +//sampleStart |
| 1794 | +fun foo(): Flow<Int> = flow { |
| 1795 | + for (i in 1..5) { |
| 1796 | + println("Emitting $i") |
| 1797 | + emit(i) |
| 1798 | + } |
| 1799 | +} |
| 1800 | + |
| 1801 | +fun main() = runBlocking<Unit> { |
| 1802 | + foo().collect { value -> |
| 1803 | + if (value == 3) cancel() |
| 1804 | + println(value) |
| 1805 | + } |
| 1806 | +} |
| 1807 | +//sampleEnd |
| 1808 | +``` |
| 1809 | + |
| 1810 | +</div> |
| 1811 | + |
| 1812 | +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt). |
| 1813 | +
|
| 1814 | +We get only numbers up to 3 and a [CancellationException] after trying to emit number 4: |
| 1815 | + |
| 1816 | +```text |
| 1817 | +Emitting 1 |
| 1818 | +1 |
| 1819 | +Emitting 2 |
| 1820 | +2 |
| 1821 | +Emitting 3 |
| 1822 | +3 |
| 1823 | +Emitting 4 |
| 1824 | +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c |
| 1825 | +``` |
| 1826 | + |
| 1827 | +<!--- TEST EXCEPTION --> |
| 1828 | + |
| 1829 | +However, most other flow operators do not do additional cancellation checks on their own for performance reasons. |
| 1830 | +For example, if you use [IntRange.asFlow] extension to write the same busy loop and don't suspend anywhere, |
| 1831 | +then there are no checks for cancellation: |
| 1832 | + |
| 1833 | +<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
| 1834 | + |
| 1835 | +```kotlin |
| 1836 | +import kotlinx.coroutines.* |
| 1837 | +import kotlinx.coroutines.flow.* |
| 1838 | + |
| 1839 | +//sampleStart |
| 1840 | +fun main() = runBlocking<Unit> { |
| 1841 | + (1..5).asFlow().collect { value -> |
| 1842 | + if (value == 3) cancel() |
| 1843 | + println(value) |
| 1844 | + } |
| 1845 | +} |
| 1846 | +//sampleEnd |
| 1847 | +``` |
| 1848 | + |
| 1849 | +</div> |
| 1850 | + |
| 1851 | +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt). |
| 1852 | +
|
| 1853 | +All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`: |
| 1854 | + |
| 1855 | +```text |
| 1856 | +1 |
| 1857 | +2 |
| 1858 | +3 |
| 1859 | +4 |
| 1860 | +5 |
| 1861 | +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23 |
| 1862 | +``` |
| 1863 | + |
| 1864 | +<!--- TEST EXCEPTION --> |
| 1865 | + |
| 1866 | +#### Making busy flow cancellable |
| 1867 | + |
| 1868 | +In the case where you have a busy loop with coroutines you must explicitly check for cancellation. |
| 1869 | +You can add `.onEach { currentCoroutineContext().ensureActive() }`, but there is a ready-to-use |
| 1870 | +[cancellable] operator provided to do that: |
| 1871 | + |
| 1872 | +<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
| 1873 | + |
| 1874 | +```kotlin |
| 1875 | +import kotlinx.coroutines.* |
| 1876 | +import kotlinx.coroutines.flow.* |
| 1877 | + |
| 1878 | +//sampleStart |
| 1879 | +fun main() = runBlocking<Unit> { |
| 1880 | + (1..5).asFlow().cancellable().collect { value -> |
| 1881 | + if (value == 3) cancel() |
| 1882 | + println(value) |
| 1883 | + } |
| 1884 | +} |
| 1885 | +//sampleEnd |
| 1886 | +``` |
| 1887 | + |
| 1888 | +</div> |
| 1889 | + |
| 1890 | +> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt). |
| 1891 | +
|
| 1892 | +With the `cancellable` operator only the numbers from 1 to 3 are collected: |
| 1893 | + |
| 1894 | +```text |
| 1895 | +1 |
| 1896 | +2 |
| 1897 | +3 |
| 1898 | +Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365 |
| 1899 | +``` |
| 1900 | + |
| 1901 | +<!--- TEST EXCEPTION --> |
| 1902 | + |
1780 | 1903 | ### Flow and Reactive Streams
|
1781 | 1904 |
|
1782 | 1905 | For those who are familiar with [Reactive Streams](https://www.reactive-streams.org/) or reactive frameworks such as RxJava and project Reactor,
|
@@ -1813,6 +1936,8 @@ Integration modules include conversions from and to `Flow`, integration with Rea
|
1813 | 1936 | [Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
|
1814 | 1937 | [Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
|
1815 | 1938 | [Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
|
| 1939 | +[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html |
| 1940 | +[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html |
1816 | 1941 | <!--- INDEX kotlinx.coroutines.flow -->
|
1817 | 1942 | [Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
|
1818 | 1943 | [flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
|
@@ -1845,4 +1970,6 @@ Integration modules include conversions from and to `Flow`, integration with Rea
|
1845 | 1970 | [catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
|
1846 | 1971 | [onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
|
1847 | 1972 | [launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
|
| 1973 | +[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/kotlin.ranges.-int-range/as-flow.html |
| 1974 | +[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html |
1848 | 1975 | <!--- END -->
|
0 commit comments