| ## 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 |