Kotlin
Coroutines Labs
1
Nội dung
◼ Bài 1. Xây dựng Coroutine đầu tiên
◼ Bài 2. Coroutine Context
◼ 2.1. Dispachers
◼ 2.2. withContext
◼ 2.3. Job
◼ 2.4. Time outs
◼ 3. Async và Await
◼ 4. CoroutineScope
◼ 5. Xử lý Exception và Supervision trong Coroutine
◼ 6. Sequence trong Kotlin
◼ 7. Giới thiệu về Flow trong Kotlin Coroutines 2
Bước 1
◼ Tạo Project Kotlin coroutine example
◼ Thêm các dependencies vào
app/build.gradle.kt
implementation("org.jetbrains.kotlinx:kotlinx-
coroutines-android:1.3.9")
3
Bước 2.
◼ Tạo package firstcoroutines, tạo file
BuildFirstCoroutines.kt trong package này.
◼ Tạo hàm main.
◼ Ấn nút mũi tên xanh và chạy Run (hoặc ấn
Ctrl+Shift+F10).
4
Chương trình coroutine đầu tiên
package
vn.edu.hust.soict.gv.quangnh.coroutineexample.firstcoroutines
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
fun main() {
GlobalScope.launch {
delay(1000)
print("Hello ")
}
print("World ")
Thread.sleep(2000)
} 5
Kết quả
6
Ví dụ 2
◼ Tạo coroutine dùng runBlocking: tạo ra
coroutine và block thread hiện tại
fun main() { a. Xác định kết quả của
runBlocking { chương trình này
delay(1000)
println("Hello ")
b. Hãy hiển thị tên tiến
delay(1000) trình của từng đoạn code
println("World ") trong chương trình
}
println("After runBlocking")
}
println(“Current Thread: ${Thread.currentThread().name}")
7
Kết quả
8
Bài 2. Coroutine Context
2.1. Dispatchers
◼ Dispatchers: quyết định Thread mà Coroutine chạy
◼ Dispatchers.Default
◼ Dispatchers.IO: đọc Database, Networking
◼ Dispatchers.Main: update UI
◼ Dispatchers.Unconfined
9
Dispatcher. Ví dụ 3
◼ Tạo package coroutinecontext
◼ Tạo file TestDispatchers:
package
vn.edu.hust.soict.gv.quangnh.coroutineexample.coroutinecontext
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import
vn.edu.hust.soict.gv.quangnh.coroutineexample.MainActivity
11
object TestDispatchers {
fun runMyFirstCoroutines() {
GlobalScope.launch(Dispatchers.Default) {
Log.d(MainActivity::class.java.simpleName, "Dispatchers Default run
on ${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.IO) {
Log.d(MainActivity::class.java.simpleName, "Dispatchers IO run on
${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.Unconfined) {
Log.d(MainActivity::class.java.simpleName, "Dispatchers Unconfined
run on ${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.Main) {
Log.d(MainActivity::class.java.simpleName, "Dispatchers Main run on
${Thread.currentThread().name}")
}
}
}
12
File MainActivity.kt
package vn.edu.hust.soict.gv.quangnh.coroutineexample
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import
vn.edu.hust.soict.gv.quangnh.coroutineexample.coroutineconte
xt.TestDispatchers
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
TestDispatchers.runMyFirstCoroutines()
}
} 13
Kết quả khi chạy app
14
Nhận xét
◼ Các luồng chạy bất đồng bộ, không theo
đúng thứ tự code
◼ Dispachers Unconfined và Main đều chạy
trên Main thread. Tuy nhiên nếu Dispachers
Unconfined chạy quá lâu thì sẽ được chuyển
sang Thread mới.
15
Ví dụ 4
object TestDispachers {
fun runMyFirstCoroutines() {
GlobalScope.launch(Dispatchers.Unconfined) {
Log.d(MainActivity::class.java.simpleName, "Before delay -
Dispachers Unconfined run on ${Thread.currentThread().name}")
delay(1000)
Log.d(MainActivity::class.java.simpleName, "Dispachers
Unconfined run on ${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.Main) {
Log.d(MainActivity::class.java.simpleName, "Dispachers Main run
on ${Thread.currentThread().name}")
}
}
}
16
Kết quả
17
fun testMySecondWithContext() {
2.2. withContext
GlobalScope.launch(Dispatchers.IO) { Ví dụ 5
// Run long time task
Log.d("myLog", "Run long time task - Thread:
${Thread.currentThread().name}")
delay(2000)
withContext(Dispatchers.Main) {
// Update UI here
Log.d("myLog", "Update UI - Thread:
${Thread.currentThread().name}")
}
}
} 21
Kết quả
22
2.3. Job. Ví dụ 6
package
vn.edu.hust.soict.gv.quangnh.coroutineexample.coroutinecontext
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
23
fun main() {
val job1: Job = GlobalScope.launch {
delay(2000)
println("Hello Kotlin")
}
val job2: Job = GlobalScope.launch {
// job2 chờ đợi công việc của job1 hoàn thành rồi mới thực hiện
job1.join()
delay(1000)
println("I'm Coroutine")
}
Thread.sleep(4000)
}
24
Kết quả
25
Cancel coroutine. Ví dụ 7
fun main() {
runBlocking {
val job = launch(Dispatchers.Default) {
repeat(1000) {
delay(500)
println("I'm sleeping $it ...")
}
}
delay(1500)
job.cancel()
print("Cancelled coroutines")
}
}
26
Kết quả
27
Hàm cancelAndJoin(). Ví dụ 8
fun main() {
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // computation loop, just waste CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500
}
}
}
delay(1400) // delay a bit
println("main: I'm tired of waiting")
job.cancelAndJoin() // cancel the job and waits for its completion
println("main: Now I can quit")
} 28
Kết quả
29
Biến isActive. Ví dụ 9
fun main() {
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // computation loop, just waste CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500
}
}
}
delay(1400) // delay a bit
println("main: I'm tired of waiting")
job.cancelAndJoin() // cancel the job and waits for its completion
println("main: Now I can quit")
} 30
Kết quả
31
Coroutine với try … catch … finally
fun main() {
runBlocking {
val job = launch { Ví dụ 10
try {
repeat(1000) {
delay(100)
println("Hello Coroutine")
}
} finally {
println("Print from finally")
}
}
delay(300)
println("I want stop coroutine")
job.cancel()
}
} 32
Kết quả
33
fun main() { Hàm delay trong
khối finally
runBlocking {
val job = launch {
Ví dụ 11
try {
repeat(1000) {
delay(100)
println("Hello Coroutine")
}
} finally {
println("Print from finally")
delay(100)
println("Please print me last times")
}
}
delay(300)
println("I want stop coroutine")
job.cancel()
}
} 34
Kết quả
◼ Lý do: Hàm delay sẽ check coroutine còn
alive hay không, do đó hàm delay và các câu
lệnh sau đó không còn chạy
35
fun main() {
runBlocking {
val job = launch {
Hàm
try { withContext(NonCancellable)
Ví dụ 12
repeat(1000) {
delay(100)
println("Hello Coroutine")
}
} finally {
println("Print from finally")
withContext(NonCancellable) {
repeat(2) {
delay(100)
println("Print from NonCancellable")
}
}
}
}
delay(300)
println("I want stop coroutine")
job.cancel()
}
} 36
Kết quả
◼ Nhận xét: khối lệnh bên trong
withContext(NonCancellable) sẽ luôn được
thực hiện. 37
2.4. Timeouts. Ví dụ 13
fun main() {
runBlocking {
withTimeout(1800) {
repeat(1000) {
println("I'm sleeping $it")
delay(500)
}
}
}
}
Nghĩa là coroutine này chỉ chạy tối đa 1800 ms.
38
Kết quả
39
Xử lý Exception bằng withTimeoutOrNull
Ví dụ 14
fun main() {
runBlocking {
val result = withTimeoutOrNull(1800) {
repeat(1000) {
println("I'm sleeping $it")
delay(500)
}
"Done"
}
println("Result = $result")
}
}
40
Kết quả
41
Nếu thời gian chạy coroutine ít hơn thời
gian Timeout. Ví dụ 15
fun main() {
runBlocking {
val result = withTimeoutOrNull(1800) {
repeat(2) {
println("I'm sleeping $it")
delay(500)
}
"Done"
}
println("Result = $result")
}
}
42
Kết quả
43
3. Async và Await. Ví dụ 16
package
vn.edu.hust.soict.gv.quangnh.coroutineexample.async_await
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis
44
fun main() {
runBlocking {
val time = measureTimeMillis {
val a = doSomethingFunny1()
val b = doSomethingFunny2()
println("a + b = ${a + b}")
}
println("Time = $time")
}
}
suspend fun doSomethingFunny1(): Int {
delay(1000)
return 10
}
suspend fun doSomethingFunny2(): Int {
delay(1000)
return 20
}
45
Kết quả
◼ Như vậy thời gian chạy cần đến 2101 ms (vì
là chạy tuần tự).
◼ Có cách khác để chạy nhanh hơn, đó là sử
dụng async – await.
46
Ví dụ 17: async - await
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis
47
fun main() {
runBlocking {
val time = measureTimeMillis {
val a: Deferred<Int> = async { doSomethingFunny1() }
val b: Deferred<Int> = async { doSomethingFunny2() }
println(a.await() + b.await())
}
println("Time = $time")
}
}
suspend fun doSomethingFunny1(): Int {
delay(1000)
return 10
}
suspend fun doSomethingFunny2(): Int {
delay(1000)
return 20
}
48
Kết quả
49
4. CoroutineScope
◼ Nhận xét: cả
runBlocking, launch và
async đều chạy trong
một CoroutineScope
50
fun main() {
runBlocking { Ví dụ 18
val job1 = launch {
launch {
delay(100)
println("coroutine 1: Hello")
delay(1000)
println("coroutine 1: Goodbye")
}
launch {
delay(100)
println("coroutine 2: Hello")
delay(1000)
println("coroutine 2: Goodbye")
}
}
delay(500)
job1.cancel()
}
51
}
Kết quả
◼ Nhận xét: coroutine cha bị cancel thì
coroutine con cũng bị hủy theo.
◼ Nếu tác vụ nhất thiết phải hoàn thành kể cả
khi coroutine cha bị hủy thì dùng
GlobalScope. 52
fun main() {
runBlocking {
Ví dụ 19
val job1 = launch {
launch {
delay(100)
println("coroutine 1: Hello")
delay(1000)
println("coroutine 1: Goodbye")
}
launch {
delay(100)
println("coroutine 2: Hello")
delay(1000)
println("coroutine 2: Goodbye")
}
GlobalScope.launch { delay(500)
delay(100) job1.cancel()
println("coroutine 3: Hello") delay(2500)
delay(1000)
println("coroutine 3: Goodbye") }
} }
} 53
Kết quả
54
Ví dụ 20
fun main() {
runBlocking {
val job = launch {
repeat(3) {
delay(100)
println("coroutine: $it")
}
println("Print from parent")
}
job.join()
delay(1000)
}
}
55
Kết quả
56
Ví dụ 21
fun main() {
runBlocking {
val job = launch {
repeat(3) {
launch {
delay(100)
println("coroutine: $it")
}
}
println("Print from parent")
}
job.join()
delay(1000)
}
}
57
Kết quả
58
5. Xử lý Exception và
Supervision trong Coroutine
Ví dụ 22
fun main() {
runBlocking {
val job = GlobalScope.launch {
println("Throw Exception from Launch")
throw NullPointerException()
}
// chờ đợi coroutine hoàn thành
job.join()
val deferred = GlobalScope.async {
println("Throw Exception from Async")
throw IndexOutOfBoundsException()
}
}
} 59
Kết quả
◼ Lý do async không tạo ra các thông báo lỗi
vì các thông báo lỗi này đã được bắt bởi
biến deferred.
60
Khi thêm câu lệnh await()
fun main() {
Ví dụ 23
runBlocking {
val job = GlobalScope.launch {
println("Throw Exception from Launch")
throw NullPointerException()
}
// chờ đợi coroutine hoàn thành
job.join()
val deferred = GlobalScope.async {
println("Throw Exception from Async")
throw IndexOutOfBoundsException()
}
deferred.await()
}
} 61
Kết quả
62
fun main() {
runBlocking {
val job = GlobalScope.launch {
try {
println("Throw Exception from Launch")
throw NullPointerException()
} catch (e: NullPointerException) { Xử lý lỗi trong
println(e.toString())
} coroutine dùng
}
// chờ đợi coroutine hoàn thành
try … catch
job.join() Ví dụ 24
val deferred = GlobalScope.async {
println("Throw Exception from Async")
throw IndexOutOfBoundsException()
}
try {
deferred.await()
} catch (e: IndexOutOfBoundsException) {
println(e.toString())
63
}
Kết quả
64
fun main() {
runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Error here: ${exception.toString()}")
}
val job = GlobalScope.launch(handler) {
println("Throw Exception from Launch")
throw NullPointerException()
}
// chờ đợi coroutine hoàn thành Bắt lỗi với
job.join()
val deferred = GlobalScope.async {
CoroutineExcep
println("Throw Exception from Async") tionHandler
}
throw IndexOutOfBoundsException()
Ví dụ 25
try {
deferred.await()
}catch (e: IndexOutOfBoundsException) {
println(e.toString())
}
} 65
}
Kết quả
66
Bắt lỗi + chỉ định context
val job = GlobalScope.launch(handler + Dispatchers.Default) {
println("Throw Exception from Launch")
throw NullPointerException()
}
67
CoroutineExceptionHandler không bắt được lỗi với async
Ví dụ 26
fun main() {
runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Error here: ${exception.toString()}")
}
val job = GlobalScope.launch(handler + Dispatchers.Default) {
println("Throw Exception from Launch")
throw NullPointerException()
}
// chờ đợi coroutine hoàn thành
job.join()
val deferred = GlobalScope.async(handler) {
println("Throw Exception from Async")
throw IndexOutOfBoundsException()
}
deferred.await()
}
} 68
Kết quả
◼ Tổng kết:
◼ Sử dụng CoroutineExceptionHandler để bắt lỗi
với coroutine tạo ra bằng launch.
◼ Sử dụng try … catch để bắt lỗi với coroutine tạo
ra bằng async.
69
Nếu trong Coroutine cha có nhiều coroutine con, và các
coroutine con có khả năng tạo ra các lỗi. Ví dụ 27
◼ Khi Coroutine thứ 2 throw Exception thì các
coroutine khác sẽ dừng.
fun main() {
runBlocking {
val handle = CoroutineExceptionHandler {_, exception ->
println("Exception: $exception")
}
val job = GlobalScope.launch(handle) {
launch {
println("Coroutine 1")
delay(300)
println("Coroutine 1 continue")
throw IndexOutOfBoundsException("Coroutine 1")
} 70
launch {
println("Coroutine 2")
delay(200)
throw NullPointerException("Coroutine 2")
}
launch {
println("Coroutine 3")
delay(400)
println("Coroutine 3 continue")
throw ArithmeticException("Coroutine 3")
}
}
job.join()
delay(1000)
} // end of runBlocking
}
71
Kết quả
72
Bắt lỗi với suppressed. Ví dụ 28
fun main() {
runBlocking {
val handle = CoroutineExceptionHandler {_, exception ->
println("Exception: $exception with suppressed
${exception.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handle) {
launch {
println("Coroutine 1")
delay(300)
println("Coroutine 1 continue")
throw IndexOutOfBoundsException("Coroutine 1")
}
73
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw ArithmeticException("Coroutine 2")
}
}
launch {
println("Coroutine 3")
delay(400)
println("Coroutine 3 continue")
throw ArithmeticException("Coroutine 3")
}
}
job.join()
delay(1000)
} // end of runBlocking
}
74
Kết quả
◼ Exception aggregation: When multiple children of a
coroutine fail with an exception, the general rule is
"the first exception wins", so the first exception
gets handled.
◼ All additional exceptions that happen after the first
one are attached to the first exception as
suppressed ones. 75
SupervisorJob và SupervisorScope
Ví dụ 29
fun main() {
runBlocking {
val supervisorJob = SupervisorJob()
with(CoroutineScope(coroutineContext +
supervisorJob)) {
val firstChild = launch {
println("Print from First Child")
throw NullPointerException()
}
76
val secondChild = launch {
firstChild.join()
println("print from second Child. First Child is
Active: ${firstChild.isActive}")
try {
delay(1000)
} finally {
println("Second Child Cancelled")
}
}
firstChild.join()
println("Cancelling SupervisorJob")
supervisorJob.cancel()
secondChild.join()
}
}
} 77
Kết quả
◼ Nhận xét: secondChild vẫn chạy ngay cả khi
firstChild đã throw Exception
78
fun main() {
runBlocking {
SupervisorScope
supervisorScope {
val firstChild = launch {
Ví dụ 30
println("Print from First Child")
throw NullPointerException()
}
val secondChild = launch {
firstChild.join()
println("print from second Child. First Child is Active:
${firstChild.isActive}")
try {
delay(1000)
} finally {
println("Second Child Cancelled")
}
}
firstChild.join()
secondChild.join()
}
} 79
Kết quả
80
6. Sequence trong Kotlin
Ví dụ 31
fun foo(n: Int) : Sequence<Int> = sequence {
for (i in 0..n) {
if (i % 2 == 0)
yield(i)
}
}
fun main() {
foo(10).forEach {
println(it)
}
}
81
Kết quả
82
Kết hợp sequence với map
fun main() {
foo(10).map{it * it}.forEach {
println(it)
}
}
◼ Kết quả
83
Kết hợp sequence với filter
fun main() {
foo(10).filter { it < 8 }.forEach {
println(it)
}
}
◼ Kết quả
84
7. Giới thiệu về Flow trong Kotlin Coroutines
Ví dụ 32
fun main() {
runBlocking {
val foo = foo(200)
foo(5).collect {
println("i = $it")
}
}
}
fun foo(n : Int): Flow<Int> = flow {
for(i in 0..n) {
delay(1000)
emit(i)
}
} 85
Kết quả
◼ Nhận xét:
◼ Flow chỉ cấp dữ liệu khi cần, do đó kết
quả được in ra mà không cần đợi 200
giây do lệnh val foo = foo(200).
◼ Flow chạy bất đồng bộ nên không ảnh
hưởng đến Thread hiện tại.
86