blob: cc2ebada65d825f86b66e712f84f565ba31cd952 [file] [log] [blame] [view]
## Asynchronous work {#async}
### With return values {#async-return}
#### Kotlin
Traditionally, asynchronous work on Android that results in an output value
would use a callback; however, better alternatives exist for libraries.
Kotlin libraries should consider
[coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) and
`suspend` functions for APIs according to the following rules, but please refer
to the guidance on [allowable dependencies](#dependencies-coroutines) before
adding a new dependency on coroutines.
Kotlin suspend fun vs blocking | Behavior
------------------------------------ | --------------------------
blocking function with @WorkerThread | API is blocking
suspend | API is async (e.g. Future)
In general, do not introduce a suspend function entirely to switch threads for
blocking calls. To do so correctly requires that we allow the developer to
configure the Dispatcher. As there is already a coroutines-based API for
changing dispatchers (withContext) that the caller may use to switch threads, it
is unecessary API overhead to provide a duplicate mechanism. In addition, it
unecessary limits callers to coroutine contexts.
```kotlin
// DO expose blocking calls as blocking calls
@WorkerThread
fun blockingCall()
// DON'T wrap in suspend functions (only to switch threads)
suspend fun blockingCallWrappedInSuspend(
dispatcher: CoroutineDispatcher = Dispatchers.Default
) = withContext(dispatcher) { /* ... */ }
// DO expose async calls as suspend funs
suspend fun asyncCall(): ReturnValue
// DON'T expose async calls as a callback-based API (for the main API)
fun asyncCall(executor: Executor, callback: (ReturnValue) -> Unit)
```
#### Java
Java libraries should prefer `ListenableFuture` and the
[`CallbackToFutureAdapter`](https://developer.android.com/reference/androidx/concurrent/futures/CallbackToFutureAdapter)
implementation provided by the `androidx.concurrent:concurrent-futures` library.
Functions and methods that return `ListenableFuture` should be suffixed by,
`Async` to reserve the shorter, unmodified name for a `suspend` method or
extension function in Kotlin that returns the value normally in accordance with
structured concurrency.
Libraries **must not** use `java.util.concurrent.CompletableFuture`, as it has a
large API surface that permits arbitrary mutation of the future's value and has
error-prone defaults.
See the [Dependencies](#dependencies) section for more information on using
Kotlin coroutines and Guava in your library.
### Cancellation
Libraries that expose APIs for performing asynchronous work should support
cancellation. There are *very few* cases where it is not feasible to support
cancellation.
Libraries that use `ListenableFuture` must be careful to follow the exact
specification of
[`Future.cancel(boolean mayInterruptIfRunning)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html?is-external=true#cancel-boolean-)
behavior.
```java {.bad}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Does not support cancellation.
return false;
}
```
```java {.bad}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Aggressively does not support cancellation.
throw new UnsupportedOperationException();
}
```
```java {.good}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Pseudocode that ignores threading but follows the spec.
if (mCompleted
|| mCancelled
|| mRunning && !mayInterruptIfRunning) {
return false;
}
mCancelled = true;
return true;
}
```
### Avoid `synchronized` methods
Whenever multiple threads are interacting with shared (mutable) references those
reads and writes must be synchronized in some way. However synchronized blocks
make your code thread-safe at the expense of concurrent execution. Any time
execution enters a synchronized block or method any other thread trying to enter
a synchronized block on the same object has to wait; even if in practice the
operations are unrelated (e.g. they interact with different fields). This can
dramatically reduce the benefit of trying to write multi-threaded code in the
first place.
Locking with synchronized is a heavyweight form of ensuring ordering between
threads, and there are a number of common APIs and patterns that you can use
that are more lightweight, depending on your use case:
* Compute a value once and make it available to all threads
* Update Set and Map data structures across threads
* Allow a group of threads to process a stream of data concurrently
* Provide instances of a non-thread-safe type to multiple threads
* Update a value from multiple threads atomically
* Maintain granular control of your concurrency invariants