Asynchronous work

With return values

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 and suspend functions for APIs according to the following rules, but please refer to the guidance on allowable dependencies before adding a new dependency on coroutines.

Kotlin suspend fun vs blockingBehavior
blocking function with @WorkerThreadAPI is blocking
suspendAPI 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.

// 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 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 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) behavior.

@Override
public boolean cancel(boolean mayInterruptIfRunning) {
    // Does not support cancellation.
    return false;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
    // Aggressively does not support cancellation.
    throw new UnsupportedOperationException();
}
@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