MANUEL VIVO: Coroutines are a future of Kotlin
that simplify asynchronous operations on Android.
A new suspend modifier was introduced in the language,
and it is used for functions that need
to be run inside the coroutine.
In this video, I will tell you more
about why coroutines are important
and how they work under the hood.
This will help you better understand
why a suspend function won't return
until all the work that it has started has completed,
and also how a coroutine can suspend
without blocking threats.
If you learn something new, like the video and subscribe,
but only if you think we've earned it.
[MUSIC PLAYING]
Use coroutines to manage asynchronous tasks that
might otherwise block the main thread
and cause your app to freeze.
Coroutines are also helpful to replace callback-based APIs
with imperative looking code.
Let's see an example of asynchronous code
that uses callbacks.
Here, we have the function, loginUser
that, after making an echo request to get user information
from the internet, it saves the result to the local database
and returns the result of that call--
all of that using callbacks.
The result of the computation is returns
using the userResult callback that
is passed in as a parameter of the function.
Code that heavily uses callbacks can become
hard to read and understand.
Kotlin coroutines let you convert callback-based code
to sequential code.
Code read in sequentially is typically easier to read
and, unlike callbacks, coroutines
provide an easy way to swap between threads
and handle exceptions.
See the same function return with coroutines.
We added the suspend modifier to the function,
and now it returns user instead of having that callback we
used to pass in as a parameter.
As you can see from the wiggly icon in the code there,
the other functions call from the suspend function body
are also suspend functions.
That suspend modifier tells the compiler
that this function needs to be executed inside a coroutine.
As a developer, you can think of a suspend function
as a regular function whose execution might be suspended
and resumed at some point.
If you're new to coroutines in Android
and want to learn more about them,
I would recommend going through the coroutines Codelabs first.
But what's the compiler actually doing under the hood
when we mark the function as suspend?
Under the hood, the Kotlin compiler
takes suspended functions and converts them
to an optimized version of callbacks using a finite state
machine.
So yes, you are right, the Kotlin compiler
will write those callbacks for you.
There way suspend functions communicate with each other is
with continuation objects.
A continuation is just a generic callback interface
with some extra information.
Context will be the CoroutineContext
to be used in that continuation.
resumeWith resumes execution of the coroutine
with a result that can contain either
a value, which is the result of the computation that
caused the suspension, or an exception.
With Kotlin 1.3, you also have convenient extension functions
called resume and resumeWithException
that are specialized versions of the resumeWith function.
Back to our suspend function, how
is the compiler is going to modify it?
It will replace the suspend modifier
with an extra parameter called completion of type continuation
in the function signature.
That will be used to communicate the result
of the suspend function computation
to the coroutine that called it, as you can see in the code.
Also, the return type of the transformed function
is unit instead of user.
The user object will be returned in the added continuation
parameter.
Time out.
As a disclaimer, the code we are showing
will not fully match the bytecode
generated by the compiler.
It would be Kotlin code accurate enough
to allow you to understand what's
really happening internally.
This representation is generated by coroutines version 1.3
and might change in future versions of the library.
Back to the code again.
The Kotlin compiler will identify when the function
can suspend internally.
Every suspension point will be represented as a state
in the finite state machine.
And these states are represented with labels by the compiler.
For a better representation of the state machine,
the compiler will use a when statement to implement
the different states.
Notice that this code is incomplete,
since the different states have no way to share information.
How is that problem solved?
The compiler will use the continuation parameter
to do it.
This is why the generic of the continuation
is [INAUDIBLE] any instead of the return
type of the original function that was user.
The compiler will create a private class that, first,
holds the required data, and second,
calls the login user function recursively
to resume the execution of the function that was suspended.
Let's see what that class looks like.
Let's call that generated class, loginUserStateMachine.
It is a private class that extends
from CoroutineImpl, which is a subtype of continuation.
In the constructor, it takes this continuation object,
named Completion, that will be used to communicate back
the result of this function to the function that called it.
This is the same continuation that we called before
in the last state of the state machine.
But also, this class saves the variables
that were declared in the original suspend function.
And there are other variables that are
common for all CoroutineImpls.
The result variable is the result
from the previous continuation and label keeps
the state of the state machine.
Also, it overrides the invoke suspend
function that is used to resume the state machine.
It will call the login user function
to trigger the state machine again.
It calls it with just information of the continuation
object.
The rest of parameters in the login user function signature
become nullable.
At that point, label will be already in the next state
to execute, and the result of the previous state's
continuation will be assigned.
An instance of this class is added to login user.
The first thing it needs to do is
knowing if it is the first time the function is called
or if the function has resumed from a previous state.
It does it by checking if the continuation passed in
is of type loginUserStateMachine or not.
If it's the first time, it will create
a new loginUserStateMachine instance
and will store the completion instance
received as a parameter.
If it's not, it will just carry on executing the state machine.
For completion, this is what the rest of the function
looks like.
You can see how the rest of the code
uses the continuation variable to read
the result of the last state of the state machine.
But also, for every state, it checks
if I never happened while this function was suspended.
In the last state, it calls resume
on the continuation of the function that called this one.
And that's it.
As you can see, the Kotlin compiler
does a lot under the hood.
Because of the implementation of the generated state machine,
a suspend function won't return until all the work that it
has started has completed.
How can the code suspend without blocking the thread?
Well, everything needed to resume
the execution of a suspended function
is in the continuation object that
is passed around, so it can be resumed at any point.
That's all we have to say about suspend for now.
Thanks for watching, and go write better Android apps
with Kotlin.
[MUSIC PLAYING]