In this article
- What are coroutines?
- Blocking vs. Non-Blocking
- Kotlin Coroutines
- Suspending functions
- CoroutineScope
- Coroutine builders
- Coroutine dispatcher
- Coroutine start
- Conclusion
What are coroutines?
Coroutines have been around for quite a time now. They are in-fact one of the ideas which helped develop multitasking operating systems. A coroutine in trivial most is a subroutine or function generalisation which in non-preemptive environment (operating system or OS in short) can voluntarily yield the CPU time so that other such sub-routines can use it for themselves without losing the results of previous computations and then can continue from wherever it left. Hence coroutine basically are sub-routines which sacrifice their CPU time so that system as whole can win—Go coroutines Go.
Multitasking operating systems are type of operating systems in which multiple number of tasks can be performed concurrently (not parallelly) with the help of context switching.
Non-Preemptive OS environments are multitasking OS environments in which process yields the control (CPU time) voluntarily so that other applications can run concurrently.
Blocking Vs. Non-blocking calls
A blocking call in process or program is an instruction or list of instructions which intentionally or unintentionally blocks the thread from processing further unless and until this call completes. On the other hand if this same call has been otherwise dispatched to another thread for execution so that the main thread keep on executing asynchronously then call would have been non-blocking. In non-blocking calls the parent thread is notified with results with the help of callback.
Kotlin Coroutine
Coroutine in kotlin are implemented as short lived lightweight threads (not the threads that Thread object represents). They are dispatched with the help of a ThreadDispatcher or WorkerDispatcher and their liveness depends upon the scope in which they were created and then launched.
Lot's of languages have language level support for coroutines but what differentiates kotlin's approach from them is that koltin (according to me) provides a good interface to manage and control them. Also because kotlin is functional at heart it's more verbose and easier to write coroutine in it. Let's see a simple example
In the second line we import definition of coroutine and relevant methods into the program. Then we create and launch coroutine at line 5 with launch coroutine builder under the GlobalScope. Inside coroutine we called non-blocking suspending function delay with amount of time in milliseconds we want to delay the coroutine and at last we print “World”. Note that call to this coroutine doesn't blocks the main thread. Lastly we made a blocking call to sleep method on the main thread so that we can outlive the coroutine and receive the results back.
There are lot's of words not making sense, right?. Let's go through them.
What is Suspending function?
A suspending function as its name specifies is a function which we expect will take time to complete its execution. And hence will potentially block the thread which we don't want to do to the main thread. Kotlin has keyword suspend which indicates to the compiler that function followed by it(suspend) is a suspending function and must be executed inside a coroutine. For example delay.
Suspending functions can also be stopped in between and started again from where their execution was stopped without loosing the context as well as previously computed results.
At line 2 we indicated that this method is suspending by marking it with suspend. A suspending function can call both suspending and non-suspending functions inside it's block hence call to delay doesn't bother compiler. At line 8 (comment), if you uncomment this line then you will get an error from compiler i.e “A suspending function cannot be called from non-suspending context”. Lines from 9 through 12 we launched a new coroutine with launch coroutine builder which returns a job class instance with which you can query for various things about coroutine viz. isCompleted, isCancelled and isActive. Lastly we went inside infinite while loop which keeps on pooling the job asking for its completion until it's done. If we didn't have done it then main method would had died even before coroutine would have completed.What is Coroutine Scope?
A coroutine scope such as GlobalScope, trivially is lifetime throughout which a coroutine lives or is running. Every scope consists of coroutineContext which keep track of execution of coroutine. Every coroutine builder is an extension on CoroutineScope and hence inherits the context that come with the scope so that it could propagate both context elements and cancellation to coroutines build with it.
Coroutine builder is functional extension over coroutineScope used for building and launching coroutines.
Some common coroutine builder
- launch
- launch is fire and forget coroutine means you don't care about results; all you want to achieve is that the instructions wrapped around by launch run without blocking the main thread for example saving an image or file. However when exception happens during execution of launch, it is propagated throughout to parent potentially crashing the application.
- async
- async is executing the task asynchronously and then processing the result when it is available. It returns Deferred object's instance which is a Future with results. For example network calls.
- runBlocking
- runBlocking is for testing regular coroutine code hence it should not be used when not required. It potentially block the thread and waits for all other coroutines to complete i.e join.
What is coroutine Dispatcher?
Coroutine dispatcher is an instance of singleton abstract class CoroutineDispatcher which inherits the ContinuationInterceptor. ContinuationInterceptor is a class which keeps track of resuming coroutine after suspension cycle. It intercept the continuation and dispatches the coroutine onto thread pool. Following is list of dispatchers provided by kotlin-coroutine api.
- Dispatchers.Default
- The default CoroutineDispatcher that is used by all standard builders like launch, async, etc. It is backed by a shared pool of threads on JVM. The level of priority that these threads have is equal to amount of cores that CPU has.
- Dispatchers.IO
- This dispatcher is specifically designed for dispatching blocking IO tasks to a shared pool of threads. Additional threads in this pool are created and are shutdown on demand. The number of threads used by this dispatcher is limited by the value of “kotlinx.coroutines.io.parallelism”.
- Dispatchers.Unconfined
- Coroutines dispatched with unconfined dispatcher are not confined to any thread or pool of threads and hence they are executed in the same call-frame(thread) until the first suspension. Note that it is still experimental.
CoroutineStart
Last but not the least piece of puzzle is CoroutineStart, CoroutineStart defines the start option for coroutine in CoroutineBuilder(see signature of launch).
- CoroutineStart.DEFAULT
- It immediately schedules coroutine for execution according to its context. Normally execution case with launch and async.
- CoroutineStart.LAZY
- Coroutine starts lazily, only when it is needed. This can come in handy in cases where you first want to wait for something or some event to occur before starting executing the coroutine.
- CoroutineStart.ATOMIC
- Atomically (in a non-cancellable way) schedules coroutine for execution according to its context. That is it must execute unless some internal exception occurs.
- CoroutineStart.UNDISPATCHED
- It immediately executes coroutine until its first suspension point in the current thread. However, when coroutine is resumed from suspension it is dispatched according to the CoroutineDispatcher in its context.
Comments