Coroutines
Coroutines are not just a kotlin-specific concept, it's an Legacy concept.
There are no Coroutines in Java, but Kotlin has.
Coroutine is the structure used for asynchronous operations in long-running tasks and network operations or not to block UI.
Coroutines are also called light threads. Less energy, more work compared to threads (coroutines are in the thread pool, of course).
Code Example
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
repeat(100_000){ //we created coroutine 100.000 time
launch {
println("Coroutines are working!")
}
}
}
}
}
Output:
Coroutines are working!
Coroutines are working!
Coroutines are working!
Coroutines are working!
Coroutines are working!
//...(100000 time)
Scope: Code written inside a code block is under a scope. The codes do not work in other scopes, there is no link.
Scope in Coroutines: Scopes that determine where coroutines will be run and their lifecycle.
-Nested scopes can be used within each other.
-launch keyword: Starts a new coroutine and does not block whatever current thread it contains.
-We can start and change as many "launch" as we want.
runBlocking:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("run block started")
runBlocking {
launch { //start coroutine
println("run blocked")
delay(6000)
}
}
println("run block ended")
}
}
Output:
2022-09-11 14:56:59.279 9767-9767/me.king.androidApp I/System.out: run block started
2022-09-11 14:56:59.306 9767-9767/me.king.androidApp I/System.out: run blocked
2022-09-11 14:57:05.309 9767-9767/me.king.androidApp I/System.out: run block ended
global scope:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("run block started")
GlobalScope.launch {
delay(6000)
println("global scope")
}
println("run block ended")
}
}
Output:
2022-09-11 15:01:35.010 9932-9932/me.king.androidApp I/System.out: run block started
2022-09-11 15:01:35.045 9932-9932/me.king.androidApp I/System.out: run block ended
2022-09-11 15:01:41.050 9932-9997/me.king.androidApp I/System.out: global scope
-Runblocking and global scope are different constructs. In run blocking, the process of the code block under launch, which is blocked, is expected to finish, then other operations are performed. In global scope, the process under launch runs in the background, processes that are due continue the processes.
coroutineScope:
It is used in 2 ways. Suspend can be called within a function or in other scopes (which can be run with other coroutines).
like;
GlobalScope.launch {
coroutineScope {
...
}
}
What contexts will CoroutineScope(//Context) work with?
Coroutine(Dispatchers.Default) //Default, Main, IO, Unconfined.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Context
println("Starting")
CoroutineScope(Dispatchers.Default).launch {
delay(5000)
println("coroutine Scope ...")
}
println("Ending")
}
}
Output:
2022-09-11 15:51:19.962 10520-10520/me.king.androidApp I/System.out: Starting
2022-09-11 15:51:20.003 10520-10520/me.king.androidApp I/System.out: Ending
2022-09-11 15:51:25.009 10520-10627/me.king.androidApp I/System.out: coroutine Scope ...
CoroutineScope is similar in structure to Global scope, but differs from it by not working in the whole application.
CoroutineScope(Dispatcher.) is widely used.
nested coroutines:
fun main(){
runBlocking {
launch {
delay(5000)
println("run blocking...")
}
coroutineScope {
launch {
delay(2500)
println("it is coroutine scope")
}
}
}
}
Output:
it is coroutine scope
run blocking...
2.5 seconds coroutineScope worked then run blocking. Why didn't it block runblocking?
-Because the bottom coroutineScope is already inside runBlocking. runBlocking blocks outside.
Dispatchers:
Dispatchers can be used together.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
launch(Dispatchers.Main) {
println("Main Thread: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) {
println("IO Thread: ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Default Thread: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Unconfined Thread: ${Thread.currentThread().name}")
}
}
}
}
Output:
2022-09-11 17:09:56.286 12093-12156/me.king.androidApp I/System.out: IO Thread: DefaultDispatcher-worker-1
2022-09-11 17:09:56.286 12093-12157/me.king.androidApp I/System.out: Default Thread: DefaultDispatcher-worker-2
2022-09-11 17:09:56.286 12093-12093/me.king.androidApp I/System.out: Unconfined Thread: main
-Unconfined already worked as Main. we're already in the main, that thread didn't even come up.
SUSPEND FUN:
Suspend functions are functions that can be run in a coroutine. These functions are suspendable, they can be stopped and run at any time.
If a function is running a coroutine, it must be a suspend function. Also, if there is another function that we call this function, it should be suspend or if it is not suspend, it should be written into the coroutine scope. So we have 2 options.
fun main(){
runBlocking {
delay(2000)
println("run block working")
myfun()
}
//myfun() //error! cause main is not suspend function. if we make "suspend fun main", it will work. OR we can use this inside CoroutineScope like above.
}
suspend fun myfun() {
coroutineScope {
delay(3000)
println("suspend fun")
}
}
Output:
run block working
suspend fun
async
What happens when we use this instead of launch?
Async actually expects a response. We will pull data from the internet, we do not know what and when we will pull it, so it is used for asynchronous processing.
When we use launch, we can get an incorrect result as follows.
fun main(){
var userName = ""
var userAge = 0
runBlocking {
launch {
val downloadedName = downloadName()
userName = downloadedName
}
launch {
val downloadedAge = downloadAge()
userAge = downloadedAge
}
println("$userName $userAge")
}
}
suspend fun downloadName():String{
delay(2000)
val userName = "Ati"
println("username download")
return userName
}
suspend fun downloadAge():Int{
delay(4000)
val userAge = 23
println("userAge Download")
return userAge
}
Output:
0
username download
userAge Download
Why is this happening? Because they work at different times. That's exactly why async should be used. We will use async instead of launch to sync.
Async
fun main(){
var userName = ""
var userAge = 0
runBlocking {
val downloadedName = async {
downloadName()
}
val downloadAge = async {
downloadAge()
}
userName = downloadedName.await()
userAge = downloadAge.await()
println(userName +" "+ userAge)
}
}
suspend fun downloadName():String{
delay(2000)
val userName = "Ati"
println("username download")
return userName
}
suspend fun downloadAge():Int{
delay(4000)
val userAge = 23
println("userAge Download")
return userAge
}
Output:
username download
userAge Download
Ati 23
We made them wait with await() and keep them in sync. So we solved the problem by using async instead of suspend.
Job
We can equate launch to job and return job. We can control these returned jobs. Like canceling, starting...
fun main(){
runBlocking {
val myJob = launch {
delay(2000)
println("job")
val secondJob = launch { //we can create a second job in any job
println("job2")
}
}
myJob.invokeOnCompletion { //myJob is complete. what we doing?
println("job is complete")
}
//myJob.cancel() if we cancel myJob, secondJob is not working cause, secondJob is inside myJob.
}
Output:
job
job2
job is complete
withContext
How do we switch from one dispatcher to another?
With "withContext" we can operate in the same launch or in the same scope in different threads.
We see this most often in examples where we start with Dispatchers.IO and end with Dispatchers.Main.
fun main(){
runBlocking {
launch(Dispatchers.Default) {
println("Context: $coroutineContext")
withContext(Dispatchers.IO){
println("Context: $coroutineContext")
}
}
}
}
Output:
Context: [StandaloneCoroutine{Active}@40905687, Dispatchers.Default]
Context: [DispatchedCoroutine{Active}@69a36fb7, Dispatchers.IO]
Exception handling
In coroutines, the exceptions are caught with the CoroutineExceptionHandler, not with the try catch.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val handler = CoroutineExceptionHandler{ coroutineContext, throwable ->
println("exception -> " + throwable.localizedMessage)
}
lifecycleScope.launch(handler) {
throw Exception("fail!")
}
}
}
Output:
2022-09-12 17:45:40.639 1928-1928/me.king.androidApp I/System.out: exception -> fail!
If you have more than one launch scope and one of them fails, the scopes below will not work and will be canceled. So supervisorScope is used here.
without superviseorScope
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val handler = CoroutineExceptionHandler{ coroutineContext, throwable ->
println("exception -> " + throwable.localizedMessage)
}
lifecycleScope.launch(handler) {
launch {
throw Exception("fail!")
}
launch {
delay(2000)
println("its working")
}
}
}
}
Output:
2022-09-12 17:50:38.724 7868-7868/me.king.androidApp I/System.out: exception -> fail!
with supervisorScope
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val handler = CoroutineExceptionHandler{ coroutineContext, throwable ->
println("exception -> " + throwable.localizedMessage)
}
lifecycleScope.launch(handler) {
supervisorScope {
launch {
throw Exception("fail!")
}
launch {
delay(2000)
println("its working")
}
}
}
}
}
Output:
2022-09-12 17:53:28.288 8056-8056/me.king.androidApp I/System.out: exception -> fail!
2022-09-12 17:53:30.291 8056-8056/me.king.androidApp I/System.out: its working