• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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