-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathDelay.kt
158 lines (147 loc) · 6.79 KB
/
Delay.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
package kotlinx.coroutines
import kotlinx.coroutines.selects.*
import kotlin.coroutines.*
import kotlin.time.*
import kotlin.time.Duration.Companion.nanoseconds
/**
* This dispatcher _feature_ is implemented by [CoroutineDispatcher] implementations that natively support
* scheduled execution of tasks.
*
* Implementation of this interface affects operation of
* [delay][kotlinx.coroutines.delay] and [withTimeout] functions.
*
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
public interface Delay {
/** @suppress **/
@Deprecated(
message = "Deprecated without replacement as an internal method never intended for public use",
level = DeprecationLevel.ERROR
) // Error since 1.6.0
public suspend fun delay(time: Long) {
if (time <= 0) return // don't delay
return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
}
/**
* Schedules resume of a specified [continuation] after a specified delay [timeMillis].
*
* Continuation **must be scheduled** to resume even if it is already cancelled, because a cancellation is just
* an exception that the coroutine that used `delay` might wanted to catch and process. It might
* need to close some resources in its `finally` blocks, for example.
*
* This implementation is supposed to use dispatcher's native ability for scheduled execution in its thread(s).
* In order to avoid an extra delay of execution, the following code shall be used to resume this
* [continuation] when the code is already executing in the appropriate thread:
*
* ```kotlin
* with(continuation) { resumeUndispatchedWith(Unit) }
* ```
*/
public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)
/**
* Schedules invocation of a specified [block] after a specified delay [timeMillis].
* The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] of this invocation
* request if it is not needed anymore.
*/
public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}
/**
* Enhanced [Delay] interface that provides additional diagnostics for [withTimeout].
* Is going to be removed once there is proper JVM-default support.
* Then we'll be able put this function into [Delay] without breaking binary compatibility.
*/
@InternalCoroutinesApi
internal interface DelayWithTimeoutDiagnostics : Delay {
/**
* Returns a string that explains that the timeout has occurred, and explains what can be done about it.
*/
fun timeoutMessage(timeout: Duration): String
}
/**
* Suspends until cancellation, in which case it will throw a [CancellationException].
*
* This function returns [Nothing], so it can be used in any coroutine,
* regardless of the required return type.
*
* Usage example in callback adapting code:
*
* ```kotlin
* fun currentTemperature(): Flow<Temperature> = callbackFlow {
* val callback = SensorCallback { degreesCelsius: Double ->
* trySend(Temperature.celsius(degreesCelsius))
* }
* try {
* registerSensorCallback(callback)
* awaitCancellation() // Suspends to keep getting updates until cancellation.
* } finally {
* unregisterSensorCallback(callback)
* }
* }
* ```
*
* Usage example in (non declarative) UI code:
*
* ```kotlin
* suspend fun showStuffUntilCancelled(content: Stuff): Nothing {
* someSubView.text = content.title
* anotherSubView.text = content.description
* someView.visibleInScope {
* awaitCancellation() // Suspends so the view stays visible.
* }
* }
* ```
*/
public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {}
/**
* Delays coroutine for at least the given time without blocking a thread and resumes it after a specified time.
* If the given [timeMillis] is non-positive, this function returns immediately.
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*
* If you want to delay forever (until cancellation), consider using [awaitCancellation] instead.
*
* Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
*
* Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
* @param timeMillis time in milliseconds.
*/
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
if (timeMillis < Long.MAX_VALUE) {
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
}
/**
* Delays coroutine for at least the given [duration] without blocking a thread and resumes it after the specified time.
* If the given [duration] is non-positive, this function returns immediately.
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*
* If you want to delay forever (until cancellation), consider using [awaitCancellation] instead.
*
* Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
*
* Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
*/
public suspend fun delay(duration: Duration): Unit = delay(duration.toDelayMillis())
/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
/**
* Convert this duration to its millisecond value. Durations which have a nanosecond component less than
* a single millisecond will be rounded up to the next largest millisecond.
*/
internal fun Duration.toDelayMillis(): Long = when (isPositive()) {
true -> plus(999_999L.nanoseconds).inWholeMilliseconds
false -> 0L
}