CancellableContinuation

Cancellable continuation is a thread-safe continuation primitive with the support of an asynchronous cancellation.

Cancellable continuation can be resumed, but unlike regular Continuation, it also might be cancelled explicitly or implicitly via a parent job.

If the continuation is cancelled successfully, it resumes with a CancellationException or the specified cancel cause.

Usage

An instance of CancellableContinuation can only be obtained by the suspendCancellableCoroutine function. The interface itself is public for use and private for implementation.

A typical usages of this function is to suspend a coroutine while waiting for a result from a callback or an external source of values that optionally supports cancellation:

suspend fun <T> CompletableFuture<T>.await(): T = suspendCancellableCoroutine { c ->
val future = this
future.whenComplete { result, throwable ->
if (throwable != null) {
// Resume continuation with an exception if an external source failed
c.resumeWithException(throwable)
} else {
// Resume continuation with a value if it was computed
c.resume(result)
}
}
// Cancel the computation if the continuation itself was cancelled because a caller of 'await' is cancelled
c.invokeOnCancellation { future.cancel(true) }
}

Thread-safety

Instances of CancellableContinuation are thread-safe and can be safely shared across multiple threads. CancellableContinuation allows concurrent invocations of the cancel and resume pair, guaranteeing that only one of these operations will succeed. Concurrent invocations of resume methods lead to a IllegalStateException and are considered a programmatic error. Concurrent invocations of cancel methods is permitted, and at most one of them succeeds.

Prompt cancellation guarantee

A cancellable continuation provides a prompt cancellation guarantee.

If the Job of the coroutine that obtained a cancellable continuation was cancelled while this continuation was suspended it will not resume successfully, even if CancellableContinuation.resume was already invoked but not yet executed.

The cancellation of the coroutine's job is generally asynchronous with respect to the suspended coroutine. The suspended coroutine is resumed with a call to its Continuation.resumeWith member function or to the resume extension function. However, when the coroutine is resumed, it does not immediately start executing but is passed to its CoroutineDispatcher to schedule its execution when the dispatcher's resources become available for execution. The job's cancellation can happen before, after, and concurrently with the call to resume. In any case, prompt cancellation guarantees that the coroutine will not resume its code successfully.

If the coroutine was resumed with an exception (for example, using the Continuation.resumeWithException extension function) and cancelled, then the exception thrown by the suspendCancellableCoroutine function is determined by what happened first: exceptional resume or cancellation.

Resuming with a closeable resource

CancellableContinuation provides the capability to work with values that represent a resource that should be closed. For that, it provides resume(value: R, onCancellation: ((cause: Throwable, value: R, context: CoroutineContext) -> Unit) function that guarantees that either the given value will be successfully returned from the corresponding suspend function or that onCancellation will be invoked with the supplied value:

continuation.resume(resourceToResumeWith) { _, resourceToClose, _
// Will be invoked if the continuation is cancelled while being dispatched
resourceToClose.close()
}

Continuation states

A cancellable continuation has three observable states:

StateisActiveisCompletedisCancelled
Active (initial state)truefalsefalse
Resumed (final completed state)falsetruefalse
Canceled (final completed state)falsetruetrue

For a detailed description of each state, see the corresponding properties' documentation.

A successful invocation of cancel transitions the continuation from an active to a cancelled state, while an invocation of Continuation.resume or Continuation.resumeWithException transitions it from an active to resumed state.

Possible state transitions diagram:

    +-----------+   resume    +---------+
| Active | ----------> | Resumed |
+-----------+ +---------+
|
| cancel
V
+-----------+
| Cancelled |
+-----------+

Properties

Link copied to clipboard
abstract val isActive: Boolean

Returns true when this continuation is active -- it was created, but not yet resumed or cancelled.

Link copied to clipboard
abstract val isCancelled: Boolean

Returns true if this continuation was cancelled.

Link copied to clipboard
abstract val isCompleted: Boolean

Returns true when this continuation was completed -- resumed or cancelled.

Functions

Link copied to clipboard
abstract fun cancel(cause: Throwable? = null): Boolean

Cancels this continuation with an optional cancellation cause. The result is true if this continuation was cancelled as a result of this invocation, and false otherwise. cancel might return false when the continuation was either resumed or already cancelled.

Link copied to clipboard

Cancels a specified future when this job is cancelled. This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).

Link copied to clipboard

Registers a handler to be synchronously invoked on cancellation (regular or exceptional) of this continuation. When the continuation is already cancelled, the handler is immediately invoked with the cancellation exception. Otherwise, the handler will be invoked as soon as this continuation is cancelled.

Link copied to clipboard
abstract fun <R : T> resume(value: R, onCancellation: (cause: Throwable, value: R, context: CoroutineContext) -> Unit?)

Resumes this continuation with the specified value, calling the specified onCancellation if and only if the value was not successfully used to resume the continuation.

Link copied to clipboard

Resumes this continuation with the specified value in the invoker thread without going through the dispatch function of the CoroutineDispatcher in the context. This function is designed to only be used by CoroutineDispatcher implementations. It should not be used in general code.

Link copied to clipboard

Resumes this continuation with the specified exception in the invoker thread without going through the dispatch function of the CoroutineDispatcher in the context. This function is designed to only be used by CoroutineDispatcher implementations. It should not be used in general code.