Introduction to Kotlin coroutines
1. What is a coroutine
There are many definitions of coroutines. In the Kotlin language, a more reasonable definition of coroutines should be a thread framework (throwing thread) or a concurrent design pattern (official). It is a set of API designed by the government to facilitate multi-threaded development for developers.
2. What can coroutines do
The main purpose of coroutines is to sum up that multi-threaded code can be implemented with more elegant code. It is mainly reflected in the fact that the work that needs to be completed asynchronously and with callbacks can be completed in a synchronous manner. To give a simple example, get user information from the server and display it, the conventional writing is as follows:
api.sendRequest(object : Callback {
override fun onFail(err: Error) {
}
override fun onSucc(resp: String) {
funOnUiThread {
updateUI(resp)
}
}
})
The above is a relatively simple example, which has been nested twice. If the network request has dependencies, it will be more complicated to implement:
api.fetchUserInfo(object : Callback {
override fun onFail(err: Error) {
}
override fun onSucc(resp: String) {
api.fetchDetail(object : Callback {
override fun onFail(err: Error) {
}override fun onSucc(resp: String) {
funOnUiThread {
updateUI(resp)
}
}
})
}
})
Let’s use coroutines to achieve this job:
launch(Dispatcher. Main) {
val user = withContext(Dispatchers.IO) {
api. fetchUserInfo()
}
var detail = withContext(Dispatchers.IO) {
api. fetchUserInfo(useruser)
}
updateUI(detail)
}
You can see that the code is much simpler. The above code block can be understood as a coroutine. This code block is executed on the main thread. When requesting user information, it is switched to the IO thread to execute, because the network request is a time-consuming process. Time operation, so the IO thread is blocked, waiting for the result of the network request. At this time, the coroutine will hang and wait for the request result, but it will not block the UI thread. We can understand that it is separated from the thread (UI thread) that currently executes it and releases the current thread. When the network request gets the result, the code is remounted to the UI thread to continue execution.
From the above code, we can see that through coroutines, we can use seemingly synchronous code to complete the work that actually needs to be switched in different threads.
3. Usage of coroutine
1. Simply start a coroutine
GlobalScope. launch(Dispatchers. Main) {
Log.v("Tag", "Coroutines in Thread. currentThread(). name")
}
There are two ways to start a thread: launch and async. The above uses the launch method to run the coroutine on the main thread. The system mainly provides the following three schedulers. Main is to execute the program on the main thread, usually doing UI-related work, IO It is to run the program on the IO thread. This thread is specially optimized and suitable for disk and network I/O operations. Default is specially optimized and suitable for a large number of CPU operations.
Dispatchers.Main
Dispatchers. Default
Dispatchers.IO
2. Switch thread
After starting a coroutine, you can use withContext() to control the thread pool where the code runs without using callbacks:
GlobalScope. launch(Dispatchers. Main) {
Log.v("Tag", "xxxxxx1 in " + Thread.currentThread().name)
withContext(Dispatchers.IO) {
Thread. sleep(1000)
Log.v("Tag", "xxxxxx2 in " + Thread.currentThread().name)
}
Log.v("Tag", "xxxxxx3 in " + Thread. currentThread().name)
}
The result of running the above code:
Tag: xxxxxx1 in main
Tag: xxxxxx2 in DefaultDispatcher-worker-1
Tag: xxxxxx3 in main
We can see that the code runs on the main thread, then switches to the IO thread, and then switches back to the main thread to continue running after running. This process does not block the main thread, but just suspends the coroutine.
3. suspend function
In the thread switching example above, the current coroutine is suspended through the withContext() function, but if you want to put the work done after switching threads into an independent function, you need to declare this function as suspend. The suspend function can only be called in a coroutine or another suspend function. When the suspend function is called, the coroutine will hang up, that is, break away from the current thread, and then switch back to the original thread to continue execution after the function is executed.
GlobalScope. launch(Dispatchers. Main) {
Log.v("Tag", "xxxxxx1 in " + Thread.currentThread().name)
doIO()
Log.v("Tag", "xxxxxx3 in " + Thread.currentThread().name)
}
suspend fun doIO() {
withContext(Dispatchers.IO) {
Thread. sleep(1000)
Log.v("Tag", "xxxxxx2 in " + Thread.currentThread().name)
}
}
4. Coroutine fetchCancellation and timeout
If we start a coroutine on a page, we need to cancel the coroutine when the page is closed, or we will make an error when the coroutine switches back to the main thread to update the UI. Another scenario is that we are doing network When requesting, it is usually necessary to set a timeout period. The following is an example of coroutine cancellation and timeout:
GlobalScope. launch(Dispatchers. Main) {
var job = GlobalScope. launch(Dispatchers. Default) {
repeat(10) {
Log.v("Tag", "xxxxxx1 $it ")
delay(500)
}
}
delay(2000)
job. cancel()
//job. join()
}
The above is an example of coroutine cancellation. A coroutine is actually a job. Cancel the coroutine after calling cancel(). The join() function below is a suspend function, which means waiting for the coroutine work to complete. That is to say, wait for the job coroutine to complete before the current coroutine continues to execute.
GlobalScope. launch(Dispatchers. Main) {
var user = doIO()
Log.v("Tag", "xxxxxx result $user")
}
suspend fun doIO(): String? {
var res = withContext(Dispatchers.IO) {
withTimeoutOrNull(4000) {
delay(3000)
"done"
}
}
return res
}
The above is an example of a timeout&#xThe ff0c;withTimeoutOrNull() function indicates that if the work is completed within the specified time, it will return the following result (“done”), and if it is not completed, it will return null.
5. async and await
As mentioned earlier, after the coroutine cuts the thread or calls the suspend function, although the thread where the coroutine is located is not blocked, the coroutine is blocked (suspended). Related requests, but they are also executed after one is executed:
GlobalScope. launch(Dispatchers. Main) {
Log.v("Tag", "xxxx start")
var resp1 = doRequest1()
var resp2 = doRequest2()
Log.v("Tag", "xxxx $resp1, $resp2")
}
suspend fun doRequest1(): String? {
delay(2000)
return "resp1"
}
suspend fun doRequest2(): String? {
delay(2000)
return "resp2"
}
A request takes 2 seconds, so 2 requests here will take 4 seconds. This situation is obviously unreasonable. Kotlin can use async to start an asynchronous coroutine, and can get the return result through a suspending function await(). In this way, multiple coroutines can work in parallel:
GlobalScope. launch(Dispatchers. Main) {
Log.v("Tag", "xxxx start")
var defer1 = async {
doRequest1()
}
var defer2 = async {
doRequest2()
}
Log.v("Tag", "xxxx ${defer1. await()}, ${defer2. await()}")
}
suspend fun doRequest1(): String? {
delay(2000)
return "resp1"
}
suspend fun doRequest2(): String? {
delay(2000)
return "resp2"
}
The above starts a coroutine through async() and starts to execute the code. async returns a Deferred object, which is a subclass of Job.
6. Coroutine extension
The statement cycle of GlobalScope depends on the process, and it will exit only when the process ends. When the coroutine of GlobalScope.launch cannot be completed, you need to manually cancel(), otherwise it will cause a memory leak. For ease of use, android provides two extensions lifecycleScope and viewModelScope, which automatically bind the statement cycle. It is recommended to use lifecycleScope and viewModelScope when doing development. You only need to introduce the following dependencies before use. lifecycleScope is used in Fragment and Activity, and viewModelScope is used in ViewModel.
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'