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