Kotlin Coroutines Flashcards

1
Q

What is it Coroutine?

A

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”

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

runBlocking

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

launch

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

**

Async

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Coroutine Context

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Structured concurrency

A
  1. Each coroutine should be called within a scope and bind to that scope.
  2. Calling a coroutine from another coroutine or from the root scope creates a hierarchy of jobs within that scope.
  3. 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.
  4. 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.
  5. 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..
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Job as a context parameter

A

It is not recommended to pass the job as a context parameter to the coroutine builder because the behavior of structured concurrency becomes unintuitive.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Coroutine cancellation

A

**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
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

invokeOnCompletion

A
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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

coroutineScope

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

CoroutineExceptionHandler

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Suspending Functions

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

NonCancellable

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

CoroutineScope

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Job

A

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
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

scope.launch(someContext) {}
What context will have a new coroutine?

A

CoroutineContext = parentContext + new CoroutineJob
where
ParentContext = inheritedContext + context arguments

Internal coroutines inherit all elements of the context of the outer coroutine except for the Job. A new Job is created for them. Therefore, if the outer Job is a Supervisor, the inner job will be regular. By convention, the Job should not be passed as a Context parameter because this breaks the child-parent relationship

17
Q

SupevisorJob in a context of coroutine levels

A

SupervisorJob acts as a parent only for first-level coroutines. For second-level coroutines, the parent job will be a regular one.

18
Q

Dispatcher

A

A Dispatcher is responsible for the thread on which a coroutine will be launched:

  • Default - a background thread optimized for heavy CPU operations. A thread pool with the number of threads equal to max(2, NUM_OF_CPU_CORES).
  • IO - a background thread optimized for network requests, file reading/writing, and database operations (tasks that wait for a result). A thread pool with the number of threads equal to max(64, NUM_OF_CPU_CORES).
  • Main - the main thread.
  • Main.immediate - the main thread. It checks if the current thread is the main thread, then instead of placing the task in the queue (as in regular Main), it executes it immediately. By default, it’s better to use Main.immediate in all cases unless specific behavior of Main is required.
  • Unconfined - starts in the same thread that made the call, but after a suspend function call, it may switch to a different thread.

The dispatcher is passed to a builder, for example, launch(Dispatchers.Default), but a context can also be passed to the builder, for example, newSingleThreadContext(“MyOwnThread”), which will create its own thread for running the coroutine.

withContext(Dispatchers.IO) - switches threads. Switching between Default and IO is optimized to avoid thread switching.

19
Q

Exceptions

A

Exceptions can be caught using try-catch inside builders. It is not possible to do this from outside because the exception will be delivered up the hierarchy. From the outside, you can only wrap coroutineScope, as it rethrows the exceptions of child elements instead of passing them up the hierarchy.

try {
    coroutineScope {
        launch {
              throw IllegalStateException("Error thrown in tryCatchWithLaunch")
        }
    }
} catch (e: Exception) {
    println("Handle Exception Successfully $e")
}

In the case of async, it is best to place await within a try-catch block.

try {
    deferredResult.await()
} catch (e: Exception) {
    println("Handle Exception $e")
}

If a CoroutineExceptionHandler is passed into the scope, it will catch all exceptions from that scope. However, in practice, it is better to catch exceptions locally using try-catch. If there is a regular job and the exception was caught in try-catch, other coroutines are not cancelled, unlike when using CoroutineExceptionHandler. CoroutineExceptionHandler is usually used for logging or for restarting/stopping the application.

20
Q

SupervisorJob

A

If one coroutine throws an exception, the entire scope is immediately cancelled, and consequently, other coroutines are also cancelled. To prevent other coroutines from being cancelled, you need to use SupervisorJob.

It is important to remember that SupervisorJob works only if it is part of the scope: created with the help of supervisorScope or CoroutineScope(SupervisorJob()). If you pass SupervisorJob to a coroutine builder, there will be no effect.

21
Q

Continuation

A

Continuation, in essence, is a callback.
Continuation is an interface that contains CoroutineContext and has two methods: resumeWithResult and resumeWithException. Any implementation of coroutines is inherited from Continuation. For example, AbstractCoroutine inherits from Continuation.

A suspend function, in bytecode, is a regular function, one of whose parameters is Continuation. Using a state machine, we return to a specific point after receiving a result from Continuation.

22
Q

suspendCoroutine

A

suspendCoroutine is a special builder that suspends the current coroutine until the resumeWith or resumeWithException method is called on the Continuation.

23
Q

testing coroutines

A

runTest automatically removes all delays in coroutines, making the execution of tests faster. This builder creates a TestScope, which uses the standard test dispatcher by default. If we create new coroutines inside this builder or use withContext, it’s necessary to inject TestDispatchers to replace the real ones.

There are two types of TestDispatcher:

StandardTestDispatcher
UnconfinedTestDispatcher

When using UnconfinedTestDispatcher, a launch operation is executed first, and the external scope waits for the completion of launch, except in cases where a delay occurs inside launch. With StandardTestDispatcher, the launch operation is executed after the external block. To prevent this and ensure that the external block waits for the launch to complete, you need to use join or, for example, advanceUntilIdle.

24
Q
A