Coroutines — Android Development
1. Kotlin Coroutines
A Coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously.
While running a huge number of functions, hell lot of threads would be in use and some might be free after their process execution. So, in Kotlin concept of Co-routines is discovered. It is basically a pool of threads, then each co-routine is scheduled on a thread and co-routines re-utilizes free threads.
Kotlin coroutines on Android | Android Developers
When using runBlocking
each concurrent thread works and then the main()
function ends it task. Here a suspend function is used inside a co-routine. Also, this schedule works in a Round-Robin Fashion, this is so because this kind of scheduling works at the compiler level.
Also, on increasing the number of launch
, the number of Dispatchers
are not going to increased. The threads will remain same, until errors occur.
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
runBlocking{
launch{printList("1")}
launch{printList("2")}
launch{printList("3")}
}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%100==0){
withContext(Dispatchers.IO){
println(" $id $i")
}
}
}
}
Also, we can use GlobalScope
for the same task, the problem that occurs is that the main()
function executes way faster than the working of the coroutines.
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
GlobalScope.launch{
launch{printList("1")}
launch{printList("2")}
launch{printList("3")}
}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%100==0){
withContext(Dispatchers.IO){
println(" $id $i")
}
}
}
}
Now, if I want to wait for a particular thread to execute and then let other threads start working, I can use async{println("1")}.awit()
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
runBlocking{
async{printList("1")}.await()
launch{printList("2")}
launch{printList("3")}
}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%100==0){
withContext(Dispatchers.IO){
println(" $id $i")
}
}
}
}
2. Suspend Functions
Now, we will be focusing on the thread on which the process is being executed using that function Thread.currentThread().name
.
Here, multiple threads are provided with the tasks. Each task or looper has its own thread to work upon.
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
Thread{printList("1")}
Thread{printList("2")}
Thread{printList("3")}
Thread{printList("4")}
Thread{printList("5")}
Thread{printList("6")}
Thread{printList("7")}
Thread{printList("8")}
Thread{printList("9")}
Thread{printList("10")}
Thread{printList("11")}
Thread{printList("12")}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%1000==0){
println("looper $id iteration $i thread ${Thread.currentThread().name}")
}
}
}
Output -
If we’re using the function [Dispatcher.IO](<http://Dispatcher.IO>)
then each of the looper is working on different Dispatcher-worker.
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
runBlocking{
launch{printList("1")}
launch{printList("2")}
launch{printList("3")}
launch{printList("4")}
launch{printList("5")}
launch{printList("6")}
}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%1000==0){
withContext(Dispatchers.IO){
println("looper $id iteration $i thread ${Thread.currentThread().name}")
}
}
}
}
Output -
Here, if you Threads instead of using Coroutines then every looper works on the main thread.
val numList : ArrayList<Int>
fun main(){
for(i in 0 until 10000){
numList.add(i)
}
runBlocking{
launch{printList("1")}
launch{printList("2")}
launch{printList("3")}
launch{printList("4")}
launch{printList("5")}
launch{printList("6")}
}
}
suspend fun printList(id: String){
for(i in 0 until 10000){
if(i%1000==0){
println("looper $id iteration $i thread ${Thread.currentThread().name}")
}
}
}
Output -
3. Threads vs Dispatcher
Here it will take 2000 milliseconds to execute both of the functions. First task1()
will be executes and then task2()
will be executed.
fun main(){
task1()
task2()
}
private fun task1(){
println("Starting task 1 on ${Thread.currentThread().name}")
Thread.sleep(1000)
println("Ending task 1 on ${Thread.currentThread().name}")
}
private fun task2(){
println("Starting task 2 on ${Thread.currentThread().name}")
Thread.sleep(1000)
println("Ending task 2 on ${Thread.currentThread().name}")
}
Output -
We can use this similar kind of thing on Co-routines. Also, it would take less time even while both of them contains 1000 milliseconds of delay because here both the task1()
and task2()
executes simultaneously.
fun main(){
runBlocking{
launch{task1()}
launch{task2()}
}
}
private suspend fun task1(){
println("Starting task 1 on ${Thread.currentThread().name}")
delay(1000)
println("Ending task 1 on ${Thread.currentThread().name}")
}
private suspend fun task2(){
println("Starting task 2 on ${Thread.currentThread().name}")
delay(1000)
println("Ending task 2 on ${Thread.currentThread().name}")
}
Output -
And now if we used Threads instead of using Coroutines then they will start and end at the same time despite of having a delay of 1000 milliseconds as they will be running simultaneously.
fun main(){
println("Start = ${System.currentTimeMillis()}")
thread{ task1({println("End = ${System.currentTimeMillis()}") })}
thread{ task2({println("End = ${System.currentTimeMillis()}") })}
}
private suspend fun task1(onEnd -> Unit()){
println("Starting task 1 on ${Thread.currentThread().name}")
Thread.sleep(1000)
println("Ending task 1 on ${Thread.currentThread().name}")
onEnd()
}
private suspend fun task2(onEnd() -> Unit){
println("Starting task 2 on ${Thread.currentThread().name}")
Thread.sleep(1000)
println("Ending task 2 on ${Thread.currentThread().name}")
onEnd()
}
Output -