Last active
September 23, 2020 15:14
-
-
Save Audhil/761dd6430946b4dba5e77adff48e488d to your computer and use it in GitHub Desktop.
Coroutines in NutShell! - (pay attention to file names)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
lifecycleScope.launch(Dispatchers.IO) { | |
val time = measureTimeMillis { | |
val res1 = async { | |
println("yup 1st launched: ${Thread.currentThread().name}") | |
getNetworkResp1() | |
}.await() | |
try { | |
val res2 = async { | |
println("yup 2nd launched: ${Thread.currentThread().name}") | |
getNetworkResp2(res1) | |
}.await() | |
println("yup: finally: res1: $res1, res2: $res2") | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
println("yup, it took: $time ms!") | |
} | |
private suspend fun getNetworkResp2(res: String): String { | |
delay(1700) | |
if (res.equals("res 1", true)) | |
return "res 1" | |
throw CancellationException("WTF!") | |
} | |
private suspend fun getNetworkResp1(): String { | |
delay(1000) | |
return "res 1" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// based on https://www.youtube.com/watch?v=KWocgiYwwmM&list=PLgCYzUzKIBE_PFBRHFB_aL5stMQg3smhL&index=9&ab_channel=CodingWithMitch | |
// Coroutine Structured Concurrency, Error Handling and Exceptions | |
class ThirdActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
tvDummy.text = "ThirdActivity" | |
// #1 - if there's exception in any of the child job | |
doTheDemo1() | |
} | |
/* | |
* #0 - expected flow, | |
* | |
* 2020-09-23 01:20:34.922 21687-21835/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:20:35.915 21687-21835/com.example.croutinesdemo I/System.out: yup: res2: 4 | |
2020-09-23 01:20:36.921 21687-21835/com.example.croutinesdemo I/System.out: yup: res3: 6 | |
2020-09-23 01:20:36.921 21687-21835/com.example.croutinesdemo I/System.out: yup, all is well! | |
* */ | |
/* | |
* #1 - when there's exception any of the child job - it throws exception in all next jobs, and parent job as well | |
* | |
* when exception in 2nd child | |
* 2020-09-23 01:22:16.005 22169-22338/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:22:17.007 22169-22338/com.example.croutinesdemo I/System.out: yup: Error occured in jobB : java.lang.Exception: its bad to be exception! | |
2020-09-23 01:22:17.008 22169-22331/com.example.croutinesdemo I/System.out: yup: Error occured in jobC : kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=StandaloneCoroutine{Cancelling}@73a4bc9 | |
2020-09-23 01:22:17.009 22169-22338/com.example.croutinesdemo I/System.out: yup exception handled in one of the child job: java.lang.Exception: its bad to be exception! | |
2020-09-23 01:22:17.009 22169-22338/com.example.croutinesdemo I/System.out: yup: Error ooccured in ParJob: java.lang.Exception: its bad to be exception! | |
* | |
* | |
* | |
* when exception in 3rd child | |
* 2020-09-23 01:24:26.687 23069-23233/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:24:27.687 23069-23233/com.example.croutinesdemo I/System.out: yup: res2: 4 | |
2020-09-23 01:24:28.688 23069-23233/com.example.croutinesdemo I/System.out: yup: Error occured in jobC : java.lang.Exception: its bad to be exception! | |
2020-09-23 01:24:28.689 23069-23233/com.example.croutinesdemo I/System.out: yup exception handled in one of the child job: java.lang.Exception: its bad to be exception! | |
2020-09-23 01:24:28.689 23069-23233/com.example.croutinesdemo I/System.out: yup: Error ooccured in ParJob: java.lang.Exception: its bad to be exception! | |
* | |
* | |
* | |
* | |
* | |
* | |
# 2A - when cancellationException is thrown | |
2020-09-23 01:37:01.952 28783-28952/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:37:02.951 28783-28952/com.example.croutinesdemo I/System.out: yup: Error occurred in jobB : java.util.concurrent.CancellationException: I'm Cancellation exception! at num: 2 | |
2020-09-23 01:37:03.951 28783-28952/com.example.croutinesdemo I/System.out: yup: res3: 6 | |
2020-09-23 01:37:03.952 28783-28952/com.example.croutinesdemo I/System.out: yup, all is well! | |
* | |
* | |
* 2020-09-23 01:38:55.260 29199-29363/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:38:56.258 29199-29363/com.example.croutinesdemo I/System.out: yup: res2: 4 | |
2020-09-23 01:38:57.259 29199-29363/com.example.croutinesdemo I/System.out: yup: Error occurred in jobC : java.util.concurrent.CancellationException: I'm Cancellation exception! at num: 3 | |
2020-09-23 01:38:57.260 29199-29363/com.example.croutinesdemo I/System.out: yup, all is well! | |
* | |
* | |
* | |
* | |
* #2B - canceling job purposefully | |
* 2020-09-23 01:42:33.720 30190-30443/com.example.croutinesdemo I/System.out: yup: Error occurred in jobB : java.util.concurrent.CancellationException: I'm Cancellation: jobB | |
2020-09-23 01:42:34.526 30190-30442/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 01:42:36.725 30190-30442/com.example.croutinesdemo I/System.out: yup: res3: 6 | |
2020-09-23 01:42:36.726 30190-30442/com.example.croutinesdemo I/System.out: yup, all is well! | |
* */ | |
private val exceptionHandler = CoroutineExceptionHandler { _, exp -> | |
println("yup exception handled within parent or one of the child job: $exp") | |
} | |
private fun doTheDemo1() { | |
val parJob = CoroutineScope(Dispatchers.IO).launch(exceptionHandler) { | |
// JobA | |
val jobA = launch { | |
val res1 = doWork(1) | |
println("yup: res1: $res1") | |
} | |
jobA.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobA : $it") | |
} | |
} | |
// JobB | |
val jobB = launch { | |
val res2 = doWork(2) | |
println("yup: res2: $res2") | |
} | |
// #2B - canceling job purposefully | |
// delay(200) | |
// jobB.cancel("I'm Cancellation: jobB") | |
jobB.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobB : $it") | |
} | |
} | |
// JobC | |
val jobC = launch { | |
val res3 = doWork(3) | |
println("yup: res3: $res3") | |
} | |
jobC.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobC : $it") | |
} | |
} | |
} | |
parJob.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in ParJob: $it") | |
} else | |
println("yup, all is well!") | |
} | |
} | |
private suspend fun doWork(num: Int): Int { | |
delay(1000L * num) | |
// # 1 - exception | |
// if (num == 2) | |
// throw Exception("its bad to be exception!") | |
// # 2A - when cancellationException is thrown | |
// if (num == 3) | |
// throw CancellationException("I'm Cancellation exception! at num: $num") | |
return num * 2 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.croutinesdemo | |
import android.os.Bundle | |
import android.widget.ProgressBar | |
import android.widget.Toast | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.lifecycle.lifecycleScope | |
import kotlinx.android.synthetic.main.activity_second.* | |
import kotlinx.coroutines.* | |
// Coroutine jobs - https://youtu.be/UsHTxOILP5g?list=PLgCYzUzKIBE_PFBRHFB_aL5stMQg3smhL | |
class SecondActivity : AppCompatActivity() { | |
private lateinit var job: CompletableJob | |
private val pMax = 100 | |
private val pMin = 0 | |
private val jobTime = 4000 | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_second) | |
job_button.setOnClickListener { | |
if (!::job.isInitialized) | |
initJob() | |
job_progress_bar.startOrCancel(job) | |
} | |
} | |
private fun initJob() { | |
job = Job() | |
job.invokeOnCompletion { | |
it?.message.run { | |
if (this.isNullOrBlank()) { | |
showToast("something went wrong!") | |
return@run | |
} | |
showToast(this) | |
} | |
} | |
job_progress_bar.run { | |
max = pMax | |
progress = pMin | |
} | |
job_button.run { | |
text = "start job #1" | |
} | |
updateCompletedText("") | |
} | |
private fun updateCompletedText(text: String) { | |
lifecycleScope.launch(Dispatchers.Main) { | |
job_complete_text.text = text | |
} | |
} | |
private fun showToast(msg: String) { | |
lifecycleScope.launch(Dispatchers.Main) { | |
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show() | |
} | |
} | |
private fun ProgressBar.startOrCancel(job: CompletableJob) { | |
if (this.progress > 0) | |
resetJob() | |
else { | |
job_button.text = "cancel job #1" | |
lifecycleScope.launch(Dispatchers.IO + job) { | |
for (i in pMin..pMax) { | |
delay((jobTime / pMax).toLong()) | |
[email protected] = i | |
} | |
updateCompletedText("Job is complete!") | |
} | |
} | |
} | |
private fun resetJob() { | |
if (job.isActive || job.isCompleted) | |
job.cancel(CancellationException("yup it got cancelled!")) | |
initJob() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private var parJob: Job? = null | |
private fun globalScopeDemo1() { | |
// this works perfectly | |
/* | |
* 2020-09-22 01:39:09.724 13358-13358/com.example.croutinesdemo I/System.out: yup: work : 0, done!, in main | |
2020-09-22 01:39:09.725 13358-13358/com.example.croutinesdemo I/System.out: yup: work : 1, done!, in main | |
2020-09-22 01:39:09.726 13358-13358/com.example.croutinesdemo I/System.out: yup: it took: 1 ms! | |
* | |
on tapping cancel | |
* 2020-09-22 01:40:27.717 13358-13358/com.example.croutinesdemo I/System.out: yup job canceled: StandaloneCoroutine was cancelled, it took: 0 ms! | |
* */ | |
// click to cancel parent job | |
tvDummy.setOnClickListener { | |
parJob?.cancel() | |
} | |
val time = measureTimeMillis { | |
parJob = lifecycleScope.launch { | |
// this coroutines runs in parallel | |
launch { | |
work(0) | |
} | |
// this too | |
launch { | |
work(1) | |
} | |
} | |
} | |
parJob?.invokeOnCompletion { | |
if (it != null) { | |
println("yup job canceled: ${it.message}, it took: $time ms!") | |
} else { | |
println("yup: it took: $time ms!") | |
} | |
} | |
} | |
private fun globalScopeDemo2() { | |
// parent job gets completed, then child jobs executes | |
/* | |
2020-09-22 01:43:01.346 14435-14435/com.example.croutinesdemo I/System.out: yup: it took: 1 ms! | |
2020-09-22 01:43:04.354 14435-14531/com.example.croutinesdemo I/System.out: yup: work : 0, done!, in DefaultDispatcher-worker-3 | |
2020-09-22 01:43:04.354 14435-14534/com.example.croutinesdemo I/System.out: yup: work : 1, done!, in DefaultDispatcher-worker-5 | |
* | |
on tapping cancel, the child jobs keeps executing! - don't use GlobalScopes! | |
2020-09-22 01:43:59.926 14435-14435/com.example.croutinesdemo I/System.out: yup: it took: 1 ms! | |
2020-09-22 01:44:02.928 14435-14531/com.example.croutinesdemo I/System.out: yup: work : 0, done!, in DefaultDispatcher-worker-3 | |
2020-09-22 01:44:02.928 14435-14534/com.example.croutinesdemo I/System.out: yup: work : 1, done!, in DefaultDispatcher-worker-5 | |
* */ | |
tvDummy.setOnClickListener { | |
parJob?.cancel() | |
} | |
val time = measureTimeMillis { | |
parJob = lifecycleScope.launch { | |
// this coroutines runs as individual - doesn't care about parent job | |
GlobalScope.launch { | |
work(0) | |
} | |
// this too | |
GlobalScope.launch { | |
work(1) | |
} | |
} | |
} | |
parJob?.invokeOnCompletion { | |
if (it != null) { | |
println("yup job canceled: ${it.message}, it took: $time ms!") | |
} else { | |
println("yup: it took: $time ms!") | |
} | |
} | |
} | |
private suspend fun work(i: Int) { | |
delay(3000) | |
println("yup: work : $i, done!, in ${Thread.currentThread().name}") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 1 million coroutines | |
val c = AtomicLong() | |
for (i in 1..1000_000L) { | |
lifecycleScope.launch { | |
c.addAndGet(i) | |
} | |
} | |
println("yup: finally: ${c.get()}") // 500000500000 - possible that main thread will complete before any coroutine completes | |
// 1 million coroutines with async & await | |
val df = (1..1000_000L).map { | |
lifecycleScope.async { | |
it | |
} | |
} | |
val sumIs = runBlocking { | |
df.map { | |
it.await() | |
}.sum() | |
} | |
println("yup: it is sum: $sumIs") // 500000500000 - main thread will wait until all the coroutine completes, runBlocking{} is used only to execute await() function in main thread | |
// 1 million coroutines with some delay - coroutines runs in parallel - it didn't work on my phone | |
val dff = (1..1000_000L).map { | |
lifecycleScope.async { | |
delay(1000) | |
it | |
} | |
} | |
val sumIss = runBlocking { | |
dff.map { | |
it.await() | |
}.sum() | |
} | |
println("yup: it is sumIss: $sumIss") // 500000500000 - main thread will wait until all the coroutine completes, runBlocking{} is used only to execute await() function in main thread |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ThirdActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
tvDummy.text = "ThirdActivity" | |
// #1 - if there's exception in any of the child job | |
// doTheDemo1() | |
// doTheDemo2() | |
} | |
// #B - when exception handler passed to child job | |
private fun doTheDemo2() { | |
/* | |
* | |
* 2020-09-23 20:39:49.838 17816-17914/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 20:39:50.839 17816-17914/com.example.croutinesdemo I/System.out: yup exception handled within parent or one of the child job: java.lang.Exception: its bad to be exception! | |
2020-09-23 20:39:50.839 17816-17914/com.example.croutinesdemo I/System.out: yup: Error occurred in jobB : java.lang.Exception: its bad to be exception! | |
2020-09-23 20:39:51.838 17816-17914/com.example.croutinesdemo I/System.out: yup: res3: 6 | |
2020-09-23 20:39:51.839 17816-17914/com.example.croutinesdemo I/System.out: yup, all is well! | |
*/ | |
val parJob = CoroutineScope(Dispatchers.IO).launch { | |
// supervisor scope with let the child jobs to handle their own exceptions or flow | |
supervisorScope { | |
// JobA | |
val jobA = launch { | |
val res1 = doWork(1) | |
println("yup: res1: $res1") | |
} | |
jobA.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobA : $it") | |
} | |
} | |
// JobB | |
val jobB = launch(exceptionHandler) { | |
val res2 = doWork(2) | |
println("yup: res2: $res2") | |
} | |
jobB.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobB : $it") | |
} | |
} | |
// JobC | |
val jobC = launch { | |
val res3 = doWork(3) | |
println("yup: res3: $res3") | |
} | |
jobC.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobC : $it") | |
} | |
} | |
} | |
} | |
parJob.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in ParJob: $it") | |
} else | |
println("yup, all is well!") | |
} | |
} | |
private val exceptionHandler = CoroutineExceptionHandler { _, exp -> | |
println("yup exception handled within parent or one of the child job: $exp") | |
} | |
// #A - when exception handler passed to parent job | |
private fun doTheDemo1() { | |
/* | |
* | |
* 2020-09-23 20:35:24.968 15793-16284/com.example.croutinesdemo I/System.out: yup: res1: 2 | |
2020-09-23 20:35:25.969 15793-16284/com.example.croutinesdemo I/System.out: yup exception handled within parent or one of the child job: java.lang.Exception: its bad to be exception! | |
2020-09-23 20:35:25.970 15793-16284/com.example.croutinesdemo I/System.out: yup: Error occurred in jobB : java.lang.Exception: its bad to be exception! | |
2020-09-23 20:35:26.967 15793-16284/com.example.croutinesdemo I/System.out: yup: res3: 6 | |
2020-09-23 20:35:26.968 15793-16284/com.example.croutinesdemo I/System.out: yup, all is well! | |
* */ | |
val parJob = CoroutineScope(Dispatchers.IO).launch(exceptionHandler) { | |
// supervisor scope with let the child jobs to handle their own exceptions or flow | |
supervisorScope { | |
// JobA | |
val jobA = launch { | |
val res1 = doWork(1) | |
println("yup: res1: $res1") | |
} | |
jobA.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobA : $it") | |
} | |
} | |
// JobB | |
val jobB = launch { | |
val res2 = doWork(2) | |
println("yup: res2: $res2") | |
} | |
// #2B - canceling job purposefully | |
// delay(200) | |
// jobB.cancel("I'm Cancellation: jobB") | |
jobB.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobB : $it") | |
} | |
} | |
// JobC | |
val jobC = launch { | |
val res3 = doWork(3) | |
println("yup: res3: $res3") | |
} | |
jobC.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in jobC : $it") | |
} | |
} | |
} | |
} | |
parJob.invokeOnCompletion { | |
if (it != null) { | |
println("yup: Error occurred in ParJob: $it") | |
} else | |
println("yup, all is well!") | |
} | |
} | |
private suspend fun doWork(num: Int): Int { | |
delay(1000L * num) | |
if (num == 2) | |
throw Exception("its bad to be exception!") | |
return num * 2 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment