信息
参考网站:https://www.kotlincn.net/docs/reference/coroutines/basics.html
回到我们的第一次编写代码,Hello,Word,也许我们在第一次初步认识的时候,发现这跟我们的线程好像没有好大的区别,但是在本质上是有很大的区别的
/**
* @description:
* @author: shu
* @createDate: 2023/8/10 20:32
* @version: 1.0
*/
import kotlinx.coroutines.*
import kotlin.concurrent.thread
@OptIn(DelicateCoroutinesApi::class)
fun main() {
GlobalScope.launch {
// 在后台启动一个新协程,并继续执行之后的代码
delay(1000L)
// 非阻塞式地延迟一秒
println("World!")
// 延迟结束后打印
}
println("Hello,")
//主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L)
// 主线程阻塞式睡眠2秒,以此来保证JVM存活
}
// 改用线程
//fun main() {
// thread {
// Thread.sleep(1000L)
// println("World!")
// }
// println("Hello,")
// Thread.sleep(2000L)
//}
本质上,协程是轻量级的线程。 它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动。 这里我们在 GlobalScope 中启动了一个新的协程,这意味着新协程的生命周期只受整个应用程序的生命周期限制。
那我们先来看看CoroutineScope?
来看看官方解释:
Defines a scope for new coroutines. Every coroutine builder (like launch, async, etc.) is an extension on CoroutineScope and inherits its coroutineContext to automatically propagate all its elements and cancellation.
The best ways to obtain a standalone instance of the scope are CoroutineScope() and MainScope() factory functions, taking care to cancel these coroutine scopes when they are no longer needed (see section on custom usage below for explanation and example).
Kotlin 中的 CoroutineScope 是用于管理协程的上下文(Context)的接口。它定义了一些方法和属性,用于启动新的协程以及管理它们的生命周期。理解 CoroutineScope 可以帮助你更好地在 Kotlin 中使用协程。
在协程中,CoroutineScope 用于:
通常,CoroutineScope 可以通过直接实现接口或使用库中提供的构建器函数(如 MainScope())来创建。在 Android 开发中,ViewModel 和 Activity 或 Fragment 通常是作为协程范围的好选择,因为它们的生命周期与协程的生命周期相匹配。
Kotlin 中的协程是一种轻量级的并发编程机制,它可以帮助你在异步任务中更简洁地处理并发操作。协程使用协程构建器来创建和启动协程。以下是一些常用的协程构建器:
GlobalScope.launch {
// 协程代码块
}
val result: Deferred<Int> = GlobalScope.async {
// 协程代码块,返回一个整数结果
42
}
runBlocking {
// 协程代码块
}
val result = withContext(Dispatchers.IO) {
// 在 IO 调度器上执行的协程代码块
// 可以进行耗时的 IO 操作
"Done"
}
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
/**
* @description:
* @author: shu
* @createDate: 2023/8/10 20:32
* @version: 1.0
*/
import kotlinx.coroutines.*
import kotlin.concurrent.thread
@OptIn(DelicateCoroutinesApi::class)
fun main() {
val tem= GlobalScope.launch {
// 在后台启动一个新协程,并继续执行之后的代码
delay(1000L)
// 非阻塞式地延迟一秒
println("World!")
// 延迟结束后打印
}
println(tem)
// 改变协程的状态
tem.cancel()
// 打印
println(tem)
println("Hello,")
//主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L)
// 主线程阻塞式睡眠2秒,以此来保证JVM存活
}
// 改用线程
//fun main() {
// thread {
// Thread.sleep(1000L)
// println("World!")
// }
// println("Hello,")
// Thread.sleep(2000L)
//}
我们在想一下它肯定有一个生命周期的概念,或许他维护者一个状态,这个就是下一篇文章我们来探讨的,我们还是回到我们的主题,介绍协程构建器
import kotlinx.coroutines.*
/**
* @description:
* @author: shu
* @createDate: 2023/8/11 15:02
* @version: 1.0
*/
class AsyncTest {
companion object {
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
@JvmStatic
fun main(args: Array<String>) {
// async 与 launch 的区别在于,async 会返回一个 Deferred 对象
// 该对象可以通过 await() 方法来获取最终的计算结果
val result: Deferred<String> = GlobalScope.async {
delay(1000L)
"World!"
}
println("Hello,")
Thread.sleep(2000L)
// 主线程阻塞式睡眠2秒,以此来保证JVM存活
println(result.getCompleted())
}
}
}
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
参数与上面的launch都是一样的最大的区别在于返回对象不就一样,Deferred
下面的创建我就不说了,与上面的大体过程一样,无非是创建的返回对象不一样罢了,那他可以终止不?
fun main() = runBlocking {
val deferred = GlobalScope.async {
// 协程代码块
for (i in 1..10) {
println("Step $i")
delay(1000)
}
"Done"
}
// 等待一段时间,然后取消协程
delay(5000)
deferred.cancel()
// 等待协程执行完成
val result = deferred.await()
println("Result: $result")
}
Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
运行一个新的协程并且阻塞当前可中断的线程直至协程执行完成,该函数不应从一个协程中使用,该函数被设计用于桥接普通阻塞代码到以挂起风格(suspending style)编写的库,以用于主函数与测试。
import kotlinx.coroutines.*
/**
* @description:
* @author: shu
* @createDate: 2023/8/11 15:28
* @version: 1.0
*/
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val job = GlobalScope.launch {
// 在后台启动一个新协程,并继续执行之后的代码
delay(1000L)
// 非阻塞式地延迟一秒
println("World!")
// 延迟结束后打印
}
println("Hello,")
//主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L)
// 主线程阻塞式睡眠2秒,以此来保证JVM存活
}
@Throws(InterruptedException::class)
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
需要注意的是,runBlocking 主要用于测试和调试,或者在 main 函数中执行一些协程代码。在正式的应用程序中,通常更推荐使用其他协程构建器,如 launch 和 async,以更好地管理协程的作用域和并发执行。
withContext 是 Kotlin 协程库中的一个协程构建器,用于在指定的调度器上执行协程代码块。它在切换协程的上下文时非常有用,特别是在需要在不同的线程或调度器上执行代码块时。
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
/**
* @description:
* @author: shu
* @createDate: 2023/8/11 15:56
* @version: 1.0
*/
suspend fun main(){
val result = withContext(Dispatchers.IO) {
// 在 IO 调度器上执行的协程代码块
// 可以进行耗时的 IO 操作
"Done"
}
println(result)
}
模拟安卓请求更新
private fun fetchData() {
// 启动一个协程在后台线程执行网络请求
GlobalScope.launch(Dispatchers.Main) {
try {
val result = withContext(Dispatchers.IO) {
// 模拟网络请求,实际应用中是执行实际的网络请求操作
simulateNetworkRequest()
}
// 在主线程上更新 UI
updateUI(result)
} catch (e: Exception) {
// 处理异常
updateUI("Error: ${e.message}")
}
}
}
private suspend fun simulateNetworkRequest(): String {
// 模拟网络请求的延迟
kotlinx.coroutines.delay(2000)
return "Data from server"
}
private fun updateUI(data: String) {
resultTextView.text = data
}
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
// Copy CopyableThreadContextElement if necessary
val newContext = oldContext.newCoroutineContext(context)
// always check for cancellation of new context
newContext.ensureActive()
// FAST PATH #1 -- new context is the same as the old one
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
// `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
// There are changes in the context, so this thread needs to be updated
withCoroutineContext(coroutine.context, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// SLOW PATH -- use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont)
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}
刚开始我也看不通,就留个印象吧,我们来看看他的调度器
通过刚才的源码我们可以看到协程的启动模式中有其他设置,下面我们来看看区别
DEFAULT
package model
import kotlinx.coroutines.*
/**
* @description: 指定协程启动模式为默认模式
* @author: shu
* @createDate: 2023/8/12 10:24
* @version: 1.0
*/
class DefaultTest {
companion object {
@OptIn(DelicateCoroutinesApi::class)
@JvmStatic
fun main(args: Array<String>) {
val result= GlobalScope.launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
// 在后台启动一个新协程,并继续执行之后的代码
delay(1000L)
// 非阻塞式地延迟一秒
println("World!")
// 延迟结束后打印
}
println(result)
// 取消
result.cancel()
println("Hello,")
//主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L)
// 打印协程的状态
println(result)
}
}
}
package model
import kotlinx.coroutines.*
/**
* @description: 指定协程启动模式为懒加载模式,只有在调用 start 或者 join 的时候才会真正启动协程
* @author: shu
* @createDate: 2023/8/12 10:34
* @version: 1.0
*/
class LazyTest {
companion object {
@OptIn(DelicateCoroutinesApi::class)
@JvmStatic
fun main(args: Array<String>) {
val result= GlobalScope.launch(Dispatchers.Default, CoroutineStart.LAZY) {
// 在后台启动一个新协程,并继续执行之后的代码
delay(1000L)
// 非阻塞式地延迟一秒
println("World!")
// 延迟结束后打印
}
// 需要手动启动
result.start()
println(result)
println("Hello,")
//主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L)
}
}
}
package model
import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicInteger
/**
* @description:
* @author: shu
* @createDate: 2023/8/12 10:37
* @version: 1.0
*/
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CoroutineStart
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
fun main() = runBlocking {
println("Main start")
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
println("Coroutine start")
delay(1000)
println("Coroutine end")
}
println("Main end")
job.join()
}
我们创建了一个协程,使用CoroutineStart.ATOMIC作为启动模式。CoroutineStart.ATOMIC启动模式确保协程在启动时不会被立即执行,而是等待其他协程或操作完成后再执行。
在 Kotlin 协程中,UNDISPATCHED 是一个用于启动模式的特殊常量。它表示在当前调用栈上立即启动协程,但不会进行调度。这意味着协程会立即执行,但不会被挂起,也不会在其他线程上运行,而是在当前调用的上下文中运行。
这个启动模式通常用于一些特定的场景,比如在调用协程的代码中进行同步调用,避免协程的挂起和线程切换开销,同时仍然能够利用协程的结构来组织异步代码。
以下是一个使用 CoroutineStart.UNDISPATCHED 的示例:
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CoroutineStart
/**
* @description:
* @author: shu
* @createDate: 2023/8/12 10:37
* @version: 1.0
*/
fun main() = runBlocking {
println("Main start")
val job = launch(start = CoroutineStart.UNDISPATCHED) {
println("Coroutine start")
delay(1000)
println("Coroutine end")
}
println("Main end")
job.join()
}
协程使用了 CoroutineStart.UNDISPATCHED 启动模式。输出将会是:
Main start
Coroutine start
Main end
Coroutine end
可以看到,协程会立即启动,但是不会阻塞当前线程。由于协程使用了 delay 函数,它会在等待 1 秒后恢复执行。
当选择协程的启动模式时,需要考虑代码的逻辑和需求,以便选择最合适的模式,下面对比一下不同的启动模式,以及它们在不同情况下的使用场景和优缺点。
上面我们可以看到协程的最小执行单位就是Job,而且我们发现其中他有状态的维护,下面我们来介绍一下他的生命周期吧,我们从源码中可以发现
状态的改变
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
这个图示描述了协程(或者可以理解为一个任务)的不同状态以及它们之间的转换过程。这种状态流转可以用于管理协程的生命周期。
这些状态之间的转换顺序可能因编程语言、协程库和应用场景的不同而有所不同。状态图中的箭头表示了可能的状态转换路径,如从 “New” 到 “Active” 表示协程被启动执行,从 “Active” 到 “Completing” 表示任务即将完成,等等。
危险
在DEFAULT、ATOMIC、UNDISPATCHED这三个模式下,启动协程会进入Active状态,而在LAZY模式下启动的协程会进入New状态,需要在手动调用start方法后进入Active状态。
import kotlinx.coroutines.*
fun main() {
val job: Job = GlobalScope.launch {
println("Task started")
delay(2000)
println("Task completed")
}
println("Job state before await: ${job.state}")
runBlocking {
job.join()
println("Job state after await: ${job.state}")
}
}
结果:
Task started
Job state before await: ACTIVE
Task completed
Job state after await: COMPLETED