- Published on
Android Thread: Kiến Thức Kotlin Coroutines
- Authors
- Name
- Dan Tech
- @dan_0xff
Trong chuỗi bài viết về Threading trong Android mình đã đề cập về nhiều khái niệm: Android Thread, Looper, RxJava, Backpressure RxJava ... Bây giờ là đến lúc tìm hiểu một công cụ mạnh mẽ mới - Kotlin Coroutines
Kotlin Coroutines?
Kotlin - Ngôn ngữ lập trình; routine - lịch trình; co-routines - nhiều lịch trình phối hợp cùng nhau
Kotlin Coroutines là một bộ thư viện được viết bằng ngôn ngữ Kotlin, giúp Lập trình viên sử dụng, quản lý các luồng logic (lịch trình) bất đồng bộ với nhau một cách hiệu quả.
Các khái niệm khi sử dụng Kotlin Coroutines
CoroutineScope
Là nguồn gốc, động cơ để kích hoạt việc khởi chạy 1 Coroutine. Hãy tưởng tượng CoroutineScope giống như một factory nơi khởi tạo ra các Coroutine và quản lý vòng đời của chúng.
Trong một ứng dụng Kotlin bạn có thể tự tạo CoroutineScope cho riêng mình hoặc sử dụng GlobalScope - Một CoroutineScope với tầm hoạt động trên toàn app, khi sử dụng đến GlobalScope cần cẩn thận để tránh chiếm tài nguyên và gây ra Memory Leaks.
Trong một ứng dụng Android, bạn có thể tự tạo CoroutineScope cho riêng từng trường hợp để sử dụng, sử dụng GlobalScope hoặc là sử dụng các built-in CoroutineScope mà Android đã cung cấp sắn: lifecycleScope của Activity, Fragment; viewModelScope của ViewModel. Các built-in CoroutineScope này sẽ có vòng đời riêng, gắn liền với các Component của chúng (Activity, Fragment, ViewModel) giúp bạn thuận tiện hơn trong việc quản lý bộ nhớ và tránh phí phạm tài nguyên dễ gây Memory Leaks.
val customCoroutineScope = CoroutineScope(EmptyCoroutineContext)
customCoroutineScope.launch {
Log.d("KtCoroutines", "Hello coroutines")
}
viewModelScope.launch {
Log.d("ViewModelScope", "Hello coroutines")
}
lifecycleScope.launch {
Log.d("LifecycleScope", "Hello coroutines")
}
Job
Job trong Kotlin Coroutines là đại diện cho 1 Coroutine được tạo ra từ CoroutineScope.
Có 2 loại Job thường được dùng mặc định trong Kotlin Coroutines: Job và Deferred
Điểm giống nhau: Cả 2 đều dùng để mô tả 1 Coroutine tạo bởi CoroutineScope. Chứa dữ liệu về trạng thái của Coroutine đang chạy. Có thể được cancel khi cần thiết.
Điểm khác biệt lớn nhất giữa Job và Deferred : Job mô tả một tác vụ được thực thi nhưng không quan trọng đến kết quả trả về (fire and forget), Deferred là một tác vụ được thực thi, và chờ đợi kết quả trả về (fire and wait for result)
CoroutineContext, Dispatchers
CoroutineContext
Context: Bối cảnh
CoroutineContext được hiểu là bối cảnh, môi trường để thực thi 1 Coroutine.
Trong CoroutineContext chứa nhiều dữ liệu: Dispatcher, Parent Job, Coroutine Name, .. Trong phạm vi bài viết này hãy tập trung vào Dispatchers bạn nhé.
Dispatchers
Là kẻ quyết định Coroutine sẽ được thực thi trên Thread Pool nào. Bạn lưu ý đây là Thread Pool chứ không phải Thread nhé. Hãy lấy ví dụ sau để chứng minh
viewModelScope.launch(Dispatchers.IO) {
while (true) {
Log.d("DispatchersTest", Thread.currentThread().name)
delay(1000)
}
}
Quan sát bạn sẽ thấy Thread name được in ra màn hình sẽ được thay đổi, không giữ nguyên giá trị như cũ. Điều này chứng tỏ việc resume của Coroutine sau khi suspend ở hàm delay(1000) đã chuyển logic của Log.d sang một Thread mới. Tuy nhiên, Thread mới này vẫn thuộc quy hoạch Thread Pool của Dispatchers.IO
Kotlin cung cấp cho ứng dụng một số Dispatchers mặc định mà ta có thể lựa chọn.
- Dispatchers.Default: Là Dispatcher dùng để tạo các Coroutine Job thực thi trên Background Thread. Các task này được khuyến khích, quy định là các tác vụ tính toán, tốn nhiều tài nguyên CPU - (CPU-bound opertaion): tính toán đệ quy, tiền lãi ngân hàng, biến động thị trường, ...
- Dispatchers.IO: Là Dispatcher dùng để tạo các Coroutine Job thực thi trên môi trường Background Thread. Các task được chạy trên IO được khuyến nghị liên quan đến IO operations (thực thi ghi / đọc file, network, ...)
- Dispatchers.Main: Đại diện cho main thread trong ứng dụng. Các Coroutine được thực thi bởi Dispatchers.Main luôn chạy trên 1 Main Thread duy nhất.
- Dispatchers.Unconfined: Đặc biệt, các Coroutine Job được tạo ra bởi Unconfined sẽ không xác định được môi trường chúng sẽ được thực thi sau khi resume từ 1 suspend. Điều này có nghĩa, tại thời điểm resume hệ thống quản lý Kotlin Coroutines sẽ tìm kiếm một Thread được free gần nhất để tiếp tục thực thi, không giới hạn là Dispatchers.IO Dispatchers.Main hay Dispatchers.Default.
viewModelScope.launch(Dispatchers.Unconfined) {
while (true) {
Log.d("DispatchersTest", Thread.currentThread().name)
delay(1000)
}
}
Hãy dùng ví dụ trên cho các loại Dispatchers mà bạn muốn sử dụng để hiểu rõ hơn về cơ chế xác định Thread của Dispatchers.
Một điều lưu ý mà không ai nói cho bạn biết: Dispatchers.IO và Dispatchers.Default chia sẻ chung một số Thread trong ThreadPool. Điều này giúp cho việc switch context giảm bớt tỷ lệ janking, và tăng khả năng tối ưu hiệu năng. Nguồn: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
Suspend Function
suspend function là một loại function đặc biệt của Kotlin Coroutines. Đúng như cái tên của nó, suspend function có khả năng được suspend (gián đoạn, dừng lại) và tiếp tục resume sau đó để tiếp tục thực thi logic.
- suspend function chỉ có thể được thực thi bên trong một suspend function khác.
fun normalFunction() {
delay(1000) // -> error
newSuspendFunction() // -> error
coroutineScope.launch {
newSuspendFunction() // -> not error
}
}
suspend fun newSuspendFunction() {
Log.d("Running new suspend function")
anotherSuspendFunction() // -> not error
}
suspend fun anotherSuspendFunction() {
delay(1000) // -> not error
}
val userApi: UserApi by lazy { get() }
// Real usecase của suspend function
suspend fun fetchFullUserProfile(userId: String): FullUserProfile {
val remoteUserProfile = async { userApi.getUser(userId) }.await()
val userSetting = async { userApi.getUserSetting(userId) }.await()
return FullUserProfile(remoteUserProfile, userSetting)
}
So sánh Kotlin Coroutines và RxJava
Mối liên hệ giữa Kotlin Coroutines và RxJava (Câu hỏi phỏng vấn)