Hi folks,
In this repo, we will see how a kotlin coroutine asynchronous call differs from a regular function execution.
This repo also contains
- How a coroutine works internally.
- Different classes involved in the construct of a suspending fiunctions and their roles
- How the bytecode is generated for each suspending functions with the classes we discussed in 2. (Basic and advanced implementation).
- Classes involved while switching context.
- How context switching works internally with all these classes.
- How exception is handled in a coroutine.
This is used under the hood for the asynchronous call back in coroutines which makes coroutines appear to be synchronous\
This will be implemented by the class representation of the funtion in the byte code. This helps the suspend function for state management. The variable label
is used for that.
a suspend function will only suspend if the suspending functions it calls returns COROUTINE_SUSPENDED otherwise, it will cast the result of the function to the expected type— Profile and Terms in this case—and continue executing the next label. This guarantees that no unnecessary suspensions occur.
-
CoroutineContext A CoroutineContext has a key-value store of Element objects, keyed by a class Coroutine are always started in a context which is coroutine context
CoroutineContext decides the thread on which the execution of a given block of code should happen.
CoroutineContext helps in switching between threads
Context switching is very hard in threads. Context switching means changing of threads to execute different portions of the code. \ -
CoroutineScope
Lifetime of a coroutine. Determines the lifetime/span/boundary to which a coroutine must be alive. Helps in cancellation of coroutine if the scope owner is destroyed
A coroutine can have child coroutine and the child coroutines are cancelled/ destroyed if the parent coroutine is destroyed. -
Dispatchers - Basically a thread pool. Helps to dispatch on which thread the coroutine must run. Way to define on which thread the coroutine must start.
Dispatch our coroutine to threads
Types of dispatchers available
a. IO - On the IO dispatcher there are by default 64 threads, so there could be up to 64 parallel tasks running on that dispatcher.for doing IO bound work
b. Main -
c. Default - The difference is that Dispatchers.Default is limited to the number of CPU cores (with a minimum of 2) so only N (where N == cpu cores) tasks can run in parallel in this dispatcher. For doing CPU bound work
d. Unconfined\
use Dispatchers.IO for I/O bound work or other work thattends to block, and use Dispatchers.Default for work that tends not to block.
A coroutine is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.
- This diagram shows the heirarchy of the DispatchContinuation class and how it calls the dispatcher() method of the coroutine dispatcher class.
All coroutines start with a coroutine builder. The block of code passed to the builder, along with anything called from that code block (directly or indirectly), represents the coroutine. They all create a coroutine scope
- launch - returns a refernce to a Job object.
- async - returns a deferred object which is a sub-class of Job.
- runBlocking -
- withContext - takes the dispatchers and executes the code supplied as lambda. This also can be considered as a coroutine builder as it creates a new scope just like other. This is basically used to change the dispatchers ie to execute some part of code by other thread pool. withContext not only changes context but also create a new job that is child to the parent job.
launch | async |
---|---|
Fire and forget | Perform task and return result |
launch returns a job and does not carry any resulting value |
async returns an instance of Deferred, which has an await() function that returns the result of the coroutine |
If the exception comes inside the launch block, it will crash the application if we have not handled it. | If exception comes inside the async block, it is stored inside the deferring result and is not delivered anywhere else, it will get dropped/ignored unless we handled it. |
The default behavior of a CoroutineScope is if one coroutine fails with an exception, the scope cancels all coroutines in the scope. Frequently, that is what we want. If we are doing N coroutines and need the results of all N of them to proceed, as soon as one crashes, we know that we do not need to waste the time of doing the rest of the work. However, sometimes that is not what we want. For example, suppose that we are uploading N images to a server. Just because one image upload fails does not necessarily mean that we want to abandon uploading the remaining images. Instead, we might want to complete the rest of the uploads, then find out about the failures and handle them in some way (e.g., retry policy). \
NOTE - A coroutine is not bound to be executed by a thread. It can be started, paused in one thread and resumed on another thread when it gets free from suspending work. Similarly a thread is not bound to run a single coroutine. It can run multiple coroutines. However there is a way to retrict the coroutine to single thread ie by using a single custom thread dispatcher.