-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathCoroutineScope.kt
203 lines (192 loc) · 7.53 KB
/
CoroutineScope.kt
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.experimental
import kotlinx.coroutines.experimental.internal.*
import kotlin.coroutines.experimental.*
/**
* Defines a scope for new coroutines. Every coroutine builder
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
* to automatically propagate both context elements and cancellation.
*
* [CoroutineScope] should be implemented on entities with well-defined lifecycle that are responsible
* for launching children coroutines. Example of such entity on Android is Activity.
* Usage of this interface may look like this:
*
* ```
* class MyActivity : AppCompatActivity(), CoroutineScope {
* lateinit var job: Job
* override val coroutineContext: CoroutineContext
* get() = Dispatchers.Main + job
*
* override fun onCreate(savedInstanceState: Bundle?) {
* super.onCreate(savedInstanceState)
* job = Job()
* }
*
* override fun onDestroy() {
* super.onDestroy()
* job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically
* }
*
* /*
* * Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines
* * in this method throws an exception, then all nested coroutines are cancelled.
* */
* fun loadDataFromUI() = launch { // <- extension on current activity, launched in the main thread
* val ioData = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher
* // blocking I/O operation
* }
* // do something else concurrently with I/O
* val data = ioData.await() // wait for result of I/O
* draw(data) // can draw in the main thread
* }
* }
*
* ```
*/
public interface CoroutineScope {
/**
* Returns `true` when this coroutine is still active (has not completed and was not cancelled yet).
*
* Check this property in long-running computation loops to support cancellation:
* ```
* while (isActive) {
* // do some computation
* }
* ```
*
* This property is a shortcut for `coroutineContext.isActive` in the scope when
* [CoroutineScope] is available.
* See [coroutineContext][kotlin.coroutines.experimental.coroutineContext],
* [isActive][kotlinx.coroutines.experimental.isActive] and [Job.isActive].
*
* @suppress **Deprecated**: Deprecated in favor of top-level extension property
*/
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Deprecated in favor of top-level extension property")
public val isActive: Boolean
get() = coroutineContext[Job]?.isActive ?: true
/**
* Returns the context of this scope.
*/
public val coroutineContext: CoroutineContext
}
/**
* Adds the specified coroutine context to this scope, overriding existing elements in the current
* scope's context with the corresponding keys.
*
* This is a shorthand for `CoroutineScope(thisScope + context)`.
*/
public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
CoroutineScope(context + context)
/**
* Returns `true` when current [Job] is still active (has not completed and was not cancelled yet).
*
* Check this property in long-running computation loops to support cancellation:
* ```
* while (isActive) {
* // do some computation
* }
* ```
*
* This property is a shortcut for `coroutineContext.isActive` in the scope when
* [CoroutineScope] is available.
* See [coroutineContext][kotlin.coroutines.experimental.coroutineContext],
* [isActive][kotlinx.coroutines.experimental.isActive] and [Job.isActive].
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
public val CoroutineScope.isActive: Boolean
get() = coroutineContext[Job]?.isActive ?: true
/**
* A global [CoroutineScope] not bound to any job.
*
* Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
* and are not cancelled prematurely.
* Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
*
* Application code usually should use application-defined [CoroutineScope], using
* [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
* on the instance of [GlobalScope] is highly discouraged.
*
* Usage of this interface may look like this:
*
* ```
* fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
* for (number in this) {
* send(Math.sqrt(number))
* }
* }
*
* ```
*/
object GlobalScope : CoroutineScope {
/**
* @suppress **Deprecated**: Deprecated in favor of top-level extension property
*/
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Deprecated in favor of top-level extension property")
override val isActive: Boolean
get() = true
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
/**
* Creates new [CoroutineScope] and calls the specified suspend block with this scope.
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
* context's [Job].
*
* This methods returns as soon as given block and all launched from within the scope children coroutines are completed.
* Example of the scope usages looks like this:
*
* ```
* suspend fun loadDataForUI() = coroutineScope {
*
* val data = async { // <- extension on current scope
* ... load some UI data ...
* }
*
* withContext(UI) {
* doSomeWork()
* val result = data.await()
* display(result)
* }
* }
* ```
*
* Semantics of the scope in this example:
* 1) `loadDataForUI` returns as soon as data is loaded and UI is updated.
* 2) If `doSomeWork` throws an exception, then `async` task is cancelled and `loadDataForUI` rethrows that exception.
* 3) If outer scope of `loadDataForUI` is cancelled, both started `async` and `withContext` are cancelled.
*
* Method may throw [JobCancellationException] if the current job was cancelled externally
* or may throw the corresponding unhandled [Throwable] if there is any unhandled exception in this scope
* (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
*/
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
// todo: optimize implementation to a single allocated object
val owner = ScopeOwnerCoroutine<R>(coroutineContext)
owner.start(CoroutineStart.UNDISPATCHED, owner, block)
owner.join()
if (owner.isCancelled) {
throw owner.getCancellationException().let { it.cause ?: it }
}
val state = owner.state
if (state is CompletedExceptionally) {
throw state.cause
}
@Suppress("UNCHECKED_CAST")
return state as R
}
/**
* Provides [CoroutineScope] that is already present in the current [coroutineContext] to the given [block].
* Note, this method doesn't wait for all launched children to complete (as opposed to [coroutineContext]).
*/
public suspend inline fun <R> currentScope(block: CoroutineScope.() -> R): R =
CoroutineScope(coroutineContext).block()
/**
* Creates [CoroutineScope] that wraps the given [coroutineContext].
*/
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(context)