Basic concepts and usage of Kotlin coroutines

What is a coroutine?

Coroutine is a programming idea, not limited to a specific language. In addition to Kotlin, some other languages, such as Go and Python, can implement coroutines at the language level.
Kotlin Coroutine is essentially a set of thread packaging APIs officially provided by Kotlin. Its original design intention is to solve concurrency problems and make "cooperative multitasking" more convenient to implement.

The relationship between coroutines and threads

From the perspective of Android developers to understand the relationship between them:

  • All our code runs in threads, and threads run in processes
  • Coroutines also run in threads, which can be single-threaded or multi-threaded
  • In a single thread, the total execution time of the coroutine will not be less than without the coroutine
  • On the Android system, time-consuming operations (such as network requests) are performed on the main thread, even if coroutines are used, threads need to be switched

Basic use of coroutines

Use the launch method

Coroutines are written similarly to ordinary sequential code, allowing developers to write asynchronous code in a synchronous manner. There are three ways to create a coroutine:

runBlocking {
    // 方法1:使用 runBlocking 顶层函数
}

GlobalScope.launch {
    // 方法2:使用 GlobalScope 单例对象,调用 launch 开启协程
}

val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
    // 方法3:自行通过 CoroutineContext 创建一个 CoroutineScope 对象
}
  • Method 1 is suitable for unit test scenarios and is not used in actual development because it is thread-blocked;
  • Compared with runBlocking, method 2 will not block threads, but its life cycle will be consistent with that of APP and cannot be canceled;
  • Method 3 is recommended. You can manage and control the life cycle of the coroutine through the context parameter.

The meaning of the launch method here is: create a new coroutine and run it on the specified thread. The continuous code segment passed to the launch method is called a coroutine, and the method parameter passed to the launch method can be used to specify the thread that executes this code.

coroutineScope.launch(Dispatchers.IO) {
    // 可以通过 Dispatchers.IO 参数把任务切到 IO 线程执行
}

coroutineScope.launch(Dispatchers.Main) {
    // 也可以通过 Dispatchers.Main 参数切换到主线程
}

Use the withContext method

This method can switch to the specified thread, and automatically switch the thread back to continue execution after the logic execution in the closure is completed, as shown below:

coroutineScope.launch(Dispatchers.Main) {        // 在 UI 线程开始
    val image = withContext(Dispatchers.IO) {    // 切换到 IO 线程
        getImage(imageId)                        // 在 IO 线程执行
    }
    imageView.setImageBitmap(image)              // 回到 UI 线程更新 UI
}

This method supports automatic switching back to the original thread, which can eliminate the nesting of concurrent codes during collaboration. If frequent thread switching is required, this way of writing will have a great advantage, which is "write asynchronous code in a synchronous way".

Use the suspend keyword

We can put withContext into a method alone. This method needs to be marked with the suspend keyword to compile and pass:

suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {}

Coroutines created using methods such as launch and async will detach from the thread that is executing it when a suspend method is executed. The threads and coroutines that are separated from each other will perform different tasks respectively:

  • Thread: When the thread executes to the suspend method, it will temporarily stop executing the remaining coroutine code and jump out of the coroutine code block. If it is a background thread, it will be recycled or reused by the system (continue to perform other background tasks), which is equivalent to the thread in the Java thread pool; if it is the Android main thread, it will continue to perform interface refresh tasks.
  • Coroutine: The coroutine will start from the suspended suspend method above, and continue to execute in the thread specified by the parameter of this method (such as the IO thread specified by Dispatchers.IO). After the suspend method is executed, it will switch back to its original thread. This "cut back" action is called resume in Kotlin.

The suspend keyword is just a reminder. In order for it to contain the real suspend logic, it must directly or indirectly call the suspend method that comes with Kotlin. The keyword itself has only one effect: restrict this method to be called only in a coroutine or another suspend method, otherwise it will fail to compile.

Get the return value of the coroutine

Coroutine is an asynchronous concept that requires some special operations to get the return value. To get the return value of the coroutine, you can use the following methods:

async / await

The main process is to use async to start the coroutine, and then call the await method of the Deferred object returned by async to get the result of the coroutine operation:

coroutineScope.launch(Dispatchers.IO) {
    val job = async {
        delay(1000)
        return@async "return value"
    }
    println("async result=${job.await()}")
}

suspendCoroutine

Different from async, suspendCoroutine is just a suspend method, it cannot start the coroutine, and needs to be used in other coroutine scopes. After the coroutine runs, use resume to submit the return value or use resumeWithException to throw an exception.

coroutineScope.launch(Dispatchers.IO) {
    try {
        val result = suspendCoroutine<String> {
            delay(1000)
            val random = Random().nextBoolean()
            if (random) {
                it.resume("return value")
            } else {
                it.resumeWithException(Exception("Coroutine Failure"))
            }
        }
        println("suspendCoroutine success result: $result")
    } catch (e: java.lang.Exception) {
        println("suspendCoroutine failure exception: $e")
    }
}

Non-blocking suspension of coroutines

"Non-blocking suspension" refers to the fact that the coroutine cuts threads while suspending. Code that uses coroutines may seem to block, but because coroutines do a lot of work inside (including automatically switching threads), it is actually non-blocking. In the process of code execution, although the thread will switch, but the writing is similar to ordinary single-threaded code.
In Kotlin, a coroutine is a higher-level tool API implemented based on threads, similar to the Handler series APIs that come with Android. In terms of design ideas, coroutines are a thread-based upper-level framework. Kotlin coroutines do not break away from Kotlin or JVM to create new things, but simplify multi-threaded development.

code example

Use a coroutine to simulate a network request, display Loading while waiting, make the Loading disappear if the request succeeds or an error occurs, and feedback the status to the user.
Write the following business logic code in ViewModel:

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    enum class RequestStatus {
        IDLE, LOADING, SUCCESS, FAIL
    }
    
    val requestStatus = MutableStateFlow(RequestStatus.IDLE)
    
    /**
     * 模拟网络请求,并将状态设置给 requestStatus 变量
     */
    fun simulateNetworkRequest() {
        requestStatus.value = RequestStatus.LOADING
        viewModelScope.launch {
            val requestResult = async { performSimulatedRequest() }.await()
            requestStatus.value = if (requestResult) RequestStatus.SUCCESS else RequestStatus.FAIL
        }
    }
    
    /**
     * 使用 delay 方法模拟耗时操作,用随机数模拟请求成功或失败
     */
    private suspend fun performSimulatedRequest() = withContext(Dispatchers.IO) {
        delay(500)
        val random = Random()
        return@withContext random.nextBoolean()
    }
}

Use Jetpack Compose in MainActivity to display the request status on the interface in real time:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    private val mainViewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    val requestStatusState = mainViewModel.requestStatus.collectAsState()
                    val requestStatus by rememberSaveable { requestStatusState }
                    
                    Text(
                        text = requestStatus.name,
                        color = Color.Red
                    )
                }
            }
        }
        mainViewModel.simulateNetworkRequest()
    }
}

Guess you like

Origin blog.csdn.net/qq_39312146/article/details/130957738
Recommended