- vừa được xem lúc

Cùng học Kotlin Coroutine, phần 3: Coroutine Context và Dispatcher

0 0 29

Người đăng: Nguyễn Thành Minh

Theo Viblo Asia

1. Coroutine Context

Mỗi coroutine trong Kotlin đều có một context được thể hiện bằng một instance của interface CoroutineContext. Context này là một tập các element cấu hình cho coroutine.

Các loại element

Các loại element trong coroutine context gồm:

Job: nắm giữ thông tin về lifecycle của coroutine

Dispatcher: Quyết định thread nào mà coroutine sẽ chạy trên đó. Có các loại dispatcher sau:

  • Dispatchers.Main: chạy trên main UI thread

  • Dispatchers.IO: chạy trên background thread của thread pool. Thường được dùng khi Read, write files, Database, Networking

  • Dispatchers.Default: chạy trên background thread của thread pool. Thường được dùng khi sorting a list, parse Json, DiffUtils

  • newSingleThreadContext("name_thread"): chạy trên một thread do mình đặt tên

  • newFixedThreadPoolContext(3, "name_thread"): sử dụng 3 threads trong shared background thread pool

JobDispatcher là 2 element chính trong CoroutineContext. Ngoài ra còn một số element khác như:

CoroutineName("name"): đặt tên cho coroutine

NonCancellable: không thể cancel kể cả khi đã gọi method cancel coroutine

Các element này sẽ được mình giải thích rõ hơn qua code example trong các mục bên dưới.

Toán thử plus (+) để thêm các element vào coroutineContext

Sử dụng toán tử cộng để set nhiều loại element cho coroutine context như sau:

// set context khi sử dụng runBlocking { } để start coroutine
runBlocking(Dispatchers.IO + Job()) {
} // hoặc set context khi sử dụng launch { } để start coroutine
GlobalScope.launch(newSingleThreadContext("demo_thread") + CoroutineName("demo_2") + NonCancellable) { }

Default Context

Nếu không set coroutine context cho coroutine thì default nó sẽ nhận Dispatchers.Default làm dispatcher và tạo ra một Job() để quản lý coroutine.

GlobalScope.launch { // tương đương với GlobalScope.launch (Dispatchers.Default + Job()) { }
}

Get coroutine context qua biến coroutineContext

Chúng ta có thể get được context coroutine thông qua property coroutineContext trong mỗi coroutine.

fun main() = runBlocking<Unit> { println("My context is: $coroutineContext")
}

Chúng ta có thể thêm các element vào một coroutineContext bằng cách sử dụng toán tử cộng +

fun main() = runBlocking<Unit> { println("A context with name: ${coroutineContext + CoroutineName("test")}")
}

2. Hàm withContext

Nó là một suspend function cho phép coroutine chạy code trong block với một context cụ thể do chúng ta quy định. Ví dụ chúng ta sẽ chạy đoạn code dưới và sẽ print ra context và thread để kiểm tra:

fun main() { newSingleThreadContext("thread1").use { ctx1 -> // tạo một context là ctx1 chứ chưa launch coroutine.  // ctx1 sẽ có 1 element là dispatcher quyết định coroutine sẽ chạy trên 1 thread tên là thread1 println("ctx1 - ${Thread.currentThread().name}") newSingleThreadContext("thread2").use { ctx2 -> // tạo một context là ctx2 chứ vẫn chưa launch coroutine // ctx2 sẽ có 1 element là dispatcher quyết định coroutine sẽ chạy trên 1 thread tên là thread2 println("ctx2 - ${Thread.currentThread().name}") // bắt đầu chạy coroutine với context là ctx1 runBlocking(ctx1) { // coroutine đang chạy trên context ctx1 và trên thread thread1 println("Started in ctx1 - ${Thread.currentThread().name}") // sử dụng hàm withContext để chuyển đổi context từ ctx1 qua ctx2 withContext(ctx2) { // coroutine đang chạy với context ctx2 và trên thread thread2 println("Working in ctx2 - ${Thread.currentThread().name}") } // coroutine đã thoát ra block withContext nên sẽ chạy lại với context ctx1 và trên thread thread1 println("Back to ctx1 - ${Thread.currentThread().name}") } } println("out of ctx2 block - ${Thread.currentThread().name}") } println("out of ctx1 block - ${Thread.currentThread().name}")
}

Output của đoạn code trên:

ctx1 - main
ctx2 - main
Started in ctx1 - thread1
Working in ctx2 - thread2
Back to ctx1 - thread1
out of ctx2 block - main
out of ctx1 block - main

Công dụng tuyệt vời của hàm withContext sẽ được chúng ta sử dụng hầu hết trong các dự án. Cụ thể chúng ta sẽ get data dưới background thread và cần UI thread để update UI:

GlobalScope.launch(Dispatchers.IO) { // do background task withContext(Dispatchers.Main) { // update UI }
}

3. Các loại Dispatcher trong Coroutine

Dispatchers and threads

Bây giờ sẽ code example để giải thích cụ thể các loại dispatcher mà mình đã giới thiệu ở trên.

fun main() = runBlocking<Unit> { launch(Dispatchers.Unconfined) { // not confined -- will work with main thread println("Unconfined : I'm working in thread ${Thread.currentThread().name}") } launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher  println("Default : I'm working in thread ${Thread.currentThread().name}") } launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}") } }

Output của đoạn code trên:

Unconfined : I'm working in thread main
Default : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread

Kết quả in ra khi sử dụng Dispatchers.DefaultnewSingleThreadContext("MyOwnThread") chẳng có gì là lạ, giống y như những gì mình đã giới thiệu về các Dispatcher ở trên 😄. Tuy nhiên Dispatchers.Unconfined là gì?. Tại sao nó lại điều phối cho coroutine chạy trên main thread. Trong khi để điều phối coroutine chạy trên main thread đã có Dispatchers.Main rồi. Vậy nó có gì đặc biệt?

Unconfined dispatcher

Để biết được Dispatchers.Unconfined khác Dispatchers.Main chỗ nào. Chúng ta sẽ cho chạy đoạn code sau:

fun main() = runBlocking { launch(Dispatchers.Unconfined) { // chưa được confined (siết lại) nên nó sẽ chạy trên main thread println("Unconfined : I'm working in thread ${Thread.currentThread().name}") delay(1000) // hàm delay() sẽ làm coroutine bị suspend sau đó resume lại println("Unconfined : After delay in thread ${Thread.currentThread().name}") } }

Ouput của đoạn code trên là:

Unconfined : I'm working in thread main
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor

Kết quả là ban đầu coroutine chạy trên main thread. Sau khi bị delay 1 giây thì chạy tiếp trên background thread chứ không phải chạy trên main thread nữa.

Bởi vì dispatcher Dispatchers.Unconfined này chạy một coroutine không giới hạn bất kỳ thread cụ thể nào. Ban đầu coroutine chưa được confined (tạm dịch là siết lại vậy 😄) thì nó sẽ chạy trên current thread. Ở đây current thread đang chạy là main thread nên nó sẽ chạy trên main thread cho đến khi nó bị suspend (ở đây ta dùng hàm delay để suspend nó). Sau khi coroutine đó resume thì nó sẽ không chạy trên current thread nữa mà chạy trên background thread.

4. Đặt tên cho coroutine

Để đặt tên cho coroutine ta sử dụng element CoroutineName(name: String) set vào coroutineContext.

GlobalScope.launch(CoroutineName("demo_2")) { // coroutine được đặt tên là demo_2
}

Kết luận

Kết thúc phần 3, hy vọng bạn đã hiểu về CoroutineContext và các element của nó. Biết cách sử dụng Dispatcher để điều phối thread cho coroutine và biết cách đặt tên coroutine bằng CoroutineName. Các element còn lại như Job, NonCancellable sẽ được mình tiếp tục giải thích trong phần tiếp theo. Cảm ơn các bạn đã theo dõi bài viết này. Hy vọng các bạn sẽ tiếp tục theo dõi những phần tiếp theo 😄

Nguồn tham khảo:

https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html

https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055

Đọc lại những phần trước:

Cùng học Kotlin Coroutine, phần 1: Giới thiệu Kotlin Coroutine và kỹ thuật lập trình bất đồng bộ

Cùng học Kotlin Coroutine, phần 2: Build first coroutine with Kotlin

Đọc tiếp phần 4: Cùng học Kotlin Coroutine, phần 4: Job, Join, Cancellation and Timeouts

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 500

- vừa được xem lúc

Cài đặt WSL / WSL2 trên Windows 10 để code như trên Ubuntu

Sau vài ba năm mình chuyển qua code trên Ubuntu thì thật không thể phủ nhận rằng mình đã yêu em nó. Cá nhân mình sử dụng Ubuntu để code web thì thật là tuyệt vời.

0 0 374

- vừa được xem lúc

Đặt tên commit message sao cho "tình nghĩa anh em chắc chắn bền lâu"????

. Lời mở đầu. .

1 1 701

- vừa được xem lúc

Tìm hiểu về Resource Controller trong Laravel

Giới thiệu. Trong laravel, việc sử dụng các route post, get, group để gọi đến 1 action của Controller đã là quá quen đối với các bạn sử dụng framework này.

0 0 335

- vừa được xem lúc

Phân quyền đơn giản với package Laravel permission

Như các bạn đã biết, phân quyền trong một ứng dụng là một phần không thể thiếu trong việc phát triển phần mềm, dù đó là ứng dụng web hay là mobile. Vậy nên, hôm nay mình sẽ giới thiệu một package có thể giúp các bạn phân quyền nhanh và đơn giản trong một website được viết bằng PHP với framework là L

0 0 421

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 414