Kotlin Coroutines Flashcards
What is it Coroutine?
A coroutine is an abstract construct representing a sequence of steps. The steps inside a coroutine are executed sequentially. The steps within a coroutine can be executed on different dispatchers (thread). Coroutine can be suspended and resumed without blocking a thread
Coroutines are “light-weight threads”
runBlocking
runBlocking is a builder that blocks the thread until the block inside it completes. It should be used only in the main function or in tests.
launch
A coroutine builder that does not block the thread is launch. It is usually known as “fire-and-forget” because it does not return a result
**
Async
To execute multiple tasks concurrently, the async builder is used, which returns a Deferred object. Deferred is a Job with a result. Deferred inherits from Job. You can obtain the result from a Deferred using the await() function. The work begins immediately upon calling async, and await blocks the coroutine but not the thread.
async does not throw an exception until the await method is called. await either returns the result or throws an exception.
You can start async lazily using async(start = CoroutineStart.LAZY). In this case, the work starts executing when await is called or by using the start() method.
Structured concurrency works with async as well. In the case of an exception or cancellation, all coroutines within the scope will complete.
Coroutine Context
In Kotlin coroutines, the Coroutine Context represents the context in which a coroutine is executed. It contains 4 main elements: Job, Coroutine Name, Dispatcher, Error Handler
Structured concurrency
- Each coroutine should be called within a scope and bind to that scope.
- Calling a coroutine from another coroutine or from the root scope creates a hierarchy of jobs within that scope.
- The parent coroutine (job) will not complete until the child coroutine finishes. When a suspend function returns, it means that all the work of that function has been completed.
- Cancelling the parent coroutine will lead to the cancellation of all its descendants in the hierarchy. Cancelling a child coroutine will not lead to the cancellation of the parent coroutine. When the entire scope is cancelled, all coroutines within it are cancelled.
- When a coroutine throws an error, its scope or psrent coroutine receive this error. And if the job is not a supervisor, then all coroutines within that scope will be cancelled..
Job as a context parameter
It is not recommended to pass the job as a context parameter to the coroutine builder because the behavior of structured concurrency becomes unintuitive.
Coroutine cancellation
**job.cancel() **- terminates the coroutine
job.cancelAndJoin() - cancels and waits for the cancelled job to complete. It block the thread. It’s good to use if you want to clean some resources in coroutine and continue after that.
It’s important to have the ability to interrupt inside the coroutine. Suspend functions should be cancellable. All function from coroutine package like delay() or yield() are cancellable functions
ensureActive() - if coroutine is cancelled it throws CancellationException
isActive - check if coroutine is active. If not you do some other operation and throw CancellationException
withContext(NonCancellable) {
// this block will be protected from cancelation
}
invokeOnCompletion
job.invokeOnCompletion { throwable -> if (throwable != null) { println("Coroutine completed with an exception: $throwable") } else { println("Coroutine completed successfully") } }
invokeOnCompletion is specifically designed for registering callbacks to be called after a coroutine completes, whereas try-finally is a general language construct used for ensuring that a block of code is executed regardless of exceptions.
coroutineScope
You can create our own scope using the coroutineScope builder, which does not block the thread but will wait for the completion of internal coroutines.
CoroutineExceptionHandler
CoroutineExceptionHandler is a last-resort mechanism for global “catch all” behavior. You cannot recover from the exception in the CoroutineExceptionHandler. The coroutine had already completed with the corresponding exception when the handler is called. Normally, the handler is used to log the exception, show some kind of error message, terminate, and/or restart the application.
Suspending Functions
A suspending function is a special type of function that can only be called inside a coroutine (directly or from another suspending function). The coroutine will wait for the suspending function to complete before moving on to the next step. Suspending functions can pause a coroutine, but do not block the thread.
NonCancellable
Using NonCancellable allows for atomic operations that are protected from interruption. NonCancellable is designed for use exclusively with withContext(). It cannot be used with launch, async, and other builders, as this would lead to unpredictable results.
CoroutineScope
CoroutineScope is a “box” that contains all the launched coroutines. It can contain either one or many coroutines. CoroutineScope tracks all coroutines, including those that are suspended. To ensure all coroutines are accounted for, launching a coroutine is not possible without a CoroutineScope. CoroutineScope can cancel all the coroutines that were started within it.
CoroutineScope and CoroutineContext are technically the same, but they are used for different purposes. CoroutineScope has only one property:
~~~
val coroutineContext: CoroutineContext
~~~
Job
Job is a representation of a coroutine. Each coroutine is associated with a Job. It is responsible for the lifecycle of the coroutine, cancellation of coroutines, and the parent-child relationship. A Job can be obtained from the context: coroutineContext[Job].
A Job can be cancelled. Cancelling a Job means cancelling the coroutine itself.
val job = scope.launch{ // some work } job.join() // suspends the current coroutine until the work is finished job.cancel() // terminates the coroutine