<lambda>null1 @file:Suppress("RedundantVisibilityModifier")
2
3 package kotlinx.coroutines.tasks
4
5 import com.google.android.gms.tasks.*
6 import kotlinx.coroutines.*
7 import java.lang.Runnable
8 import java.util.concurrent.Executor
9 import kotlin.coroutines.*
10
11 /**
12 * Converts this deferred to the instance of [Task].
13 * If deferred is cancelled then resulting task will be cancelled as well.
14 */
15 public fun <T> Deferred<T>.asTask(): Task<T> {
16 val cancellation = CancellationTokenSource()
17 val source = TaskCompletionSource<T>(cancellation.token)
18
19 invokeOnCompletion callback@{
20 if (it is CancellationException) {
21 cancellation.cancel()
22 return@callback
23 }
24
25 val t = getCompletionExceptionOrNull()
26 if (t == null) {
27 source.setResult(getCompleted())
28 } else {
29 source.setException(t as? Exception ?: RuntimeExecutionException(t))
30 }
31 }
32
33 return source.task
34 }
35
36 /**
37 * Converts this task to an instance of [Deferred].
38 * If task is cancelled then resulting deferred will be cancelled as well.
39 * However, the opposite is not true: if the deferred is cancelled, the [Task] will not be cancelled.
40 * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
41 */
asDeferrednull42 public fun <T> Task<T>.asDeferred(): Deferred<T> = asDeferredImpl(null)
43
44 /**
45 * Converts this task to an instance of [Deferred] with a [CancellationTokenSource] to control cancellation.
46 * The cancellation of this function is bi-directional:
47 * - If the given task is cancelled, the resulting deferred will be cancelled.
48 * - If the resulting deferred is cancelled, the provided [cancellationTokenSource] will be cancelled.
49 *
50 * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
51 * leads to an unspecified behaviour.
52 */
53 @ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
54 public fun <T> Task<T>.asDeferred(cancellationTokenSource: CancellationTokenSource): Deferred<T> =
55 asDeferredImpl(cancellationTokenSource)
56
57 private fun <T> Task<T>.asDeferredImpl(cancellationTokenSource: CancellationTokenSource?): Deferred<T> {
58 val deferred = CompletableDeferred<T>()
59 if (isComplete) {
60 val e = exception
61 if (e == null) {
62 if (isCanceled) {
63 deferred.cancel()
64 } else {
65 @Suppress("UNCHECKED_CAST")
66 deferred.complete(result as T)
67 }
68 } else {
69 deferred.completeExceptionally(e)
70 }
71 } else {
72 // Run the callback directly to avoid unnecessarily scheduling on the main thread.
73 addOnCompleteListener(DirectExecutor) {
74 val e = it.exception
75 if (e == null) {
76 @Suppress("UNCHECKED_CAST")
77 if (it.isCanceled) deferred.cancel() else deferred.complete(it.result as T)
78 } else {
79 deferred.completeExceptionally(e)
80 }
81 }
82 }
83
84 if (cancellationTokenSource != null) {
85 deferred.invokeOnCompletion {
86 cancellationTokenSource.cancel()
87 }
88 }
89 // Prevent casting to CompletableDeferred and manual completion.
90 return object : Deferred<T> by deferred {}
91 }
92
93 /**
94 * Awaits the completion of the task without blocking a thread.
95 *
96 * This suspending function is cancellable.
97 * If the [Job] of the current coroutine is cancelled while this suspending function is waiting, this function
98 * stops waiting for the completion stage and immediately resumes with [CancellationException].
99 *
100 * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
101 */
awaitnull102 public suspend fun <T> Task<T>.await(): T = awaitImpl(null)
103
104 /**
105 * Awaits the completion of the task that is linked to the given [CancellationTokenSource] to control cancellation.
106 *
107 * This suspending function is cancellable and cancellation is bi-directional:
108 * - If the [Job] of the current coroutine is cancelled while this suspending function is waiting, this function
109 * cancels the [cancellationTokenSource] and throws a [CancellationException].
110 * - If the task is cancelled, then this function will throw a [CancellationException].
111 *
112 * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and
113 * leads to an unspecified behaviour.
114 */
115 @ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
116 public suspend fun <T> Task<T>.await(cancellationTokenSource: CancellationTokenSource): T =
117 awaitImpl(cancellationTokenSource)
118
119 private suspend fun <T> Task<T>.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
120 // fast path
121 if (isComplete) {
122 val e = exception
123 return if (e == null) {
124 if (isCanceled) {
125 throw CancellationException("Task $this was cancelled normally.")
126 } else {
127 @Suppress("UNCHECKED_CAST")
128 result as T
129 }
130 } else {
131 throw e
132 }
133 }
134
135 return suspendCancellableCoroutine { cont ->
136 // Run the callback directly to avoid unnecessarily scheduling on the main thread.
137 addOnCompleteListener(DirectExecutor) {
138 val e = it.exception
139 if (e == null) {
140 @Suppress("UNCHECKED_CAST")
141 if (it.isCanceled) cont.cancel() else cont.resume(it.result as T)
142 } else {
143 cont.resumeWithException(e)
144 }
145 }
146
147 if (cancellationTokenSource != null) {
148 cont.invokeOnCancellation {
149 cancellationTokenSource.cancel()
150 }
151 }
152 }
153 }
154
155 /**
156 * An [Executor] that just directly executes the [Runnable].
157 */
158 private object DirectExecutor : Executor {
executenull159 override fun execute(r: Runnable) {
160 r.run()
161 }
162 }
163