Kotlin - 协程基础及原理,android应用开发基础

withContext

除了可以在 GlobalScope.launch {}、GlobalScope.async {} 创建协程时设置协程调度器,

async {...}.await() 相比 withContext 的内存开销更低,因此对于使用 async 之后立即调用 await 的情况,应当优先使用 withContext

withTimeout

Kotlin 协程提供了 withTimeout 函数设置超时取消。如果运行超时,取消后会抛出 TimeoutCancellationException 异常。抛出异常的情况下回影响到其他协程,这时候可以使用 withTimeoutOrNull 函数,它会在超时的情况下返回 null 而不抛出异常。

runBlocking {

val result = withContext(coroutineContext) {

withTimeoutOrNull(500) {

delay(1000)

“hello”

}

}

println(result)

}

// 输出结果

hello

yield

如果想要解决上面示例中的问题可以使用 yield 函数。它的作用在于检查所在协程的状态,如果已经取消,则抛出取消异常予以响应。此外它还会尝试出让线程的执行权,给其他协程提供执行机会。

在上面示例中添加 yield 函数:

if (System.currentTimeMillis() >= nextPrintTime) {

yield()

println(“job: I’m sleeping ${i++} …”)

nextPrintTime += 500L

}

// 输出结果

job: I’m sleeping 0 …

job: I’m sleeping 1 …

job: I’m sleeping 2 …

main: I’m tired of waiting!

main: Now I can quit.

协程的作用域

协程作用域:协程作用域主要用于明确协程之间的父子关系,以及对于取消或者异常处理等方面的传播行为。

协程作用域包括以下三种:

  • 顶级作用域:没有父协程的协程所在的作用域为顶级作用域。

  • 协同作用域:协程中启动新的协程,新协程为所在协程的子协程,这种情况下子协程所在的作用域默认为协同作用域。此时子协程抛出的未捕获异常将传递给父协程处理,父协程同时也会被取消。

  • 主从作用域:与协程作用域在协程的父子关系上一致,区别在于处于该作用域下的协程出现未捕获的异常时不会将异常向上传递给父协程。

父子协程间的关系:

  • 父协程被取消,则所有子协程均被取消。

  • 父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完毕。

  • 子协程会继承父协程的协程上下文元素,如果自身有相同 key 的成员,则覆盖对应的 key,覆盖的效果仅限自身范围内有效。

声明顶级作用域:GlobalScope.launch {}runBlocking {}

声明协同作用域:coroutineScope {}

声明主从作用域:supervisorScope {}

coroutineScope {}supervisorScope {} 是挂起函数所以它们只能在协程作用域中或挂起函数中调用。

coroutineScope {}supervisorScope {} 的区别在于 SupervisorCoroutine 重写了 childCancelled() 函数使异常不会向父协程传递。

协程并发

通过上文的介绍可以了解到协程其实就是执行在线程上的代码片段,所以线程的并发处理都可以用在协程上,比如 synchorinzedCAS 等。而协程本身也提供了两种方式处理并发:

  • Mutex:互斥锁;

  • Semaphore:信号量。

Mutex

Mutex 类似于 synchorinzed,协程竞争时将协程包装为 LockWaiter 使用双向链表存储。Mutex 还提供了 withLock 扩展函数,以简化使用:

runBlocking {

v
al mutex = Mutex()

var counter = 0

repeat(10000) {

GlobalScope.launch {

mutex.withLock {

counter ++

}

}

}

Thread.sleep(500) //暂停一会儿等待所有协程执行结束

println(“The final count is $counter”)

}

Semaphore

Semaphore 用以限制访问特定资源的协程数量。

runBlocking {

val semaphore = Semaphore(1)

var counter = 0

repeat(10000) {

GlobalScope.launch {

semaphore.withPermit {

counter ++

}

}

}

Thread.sleep(500) //暂停一会儿等待所有协程执行结束

println(“The final count is $counter”)

}

注意:只有在 permits = 1 时才和 Mutex 功能相同。

源码分析

suspend

我们来看 suspend 修饰函数和修饰 lambda 的区别。

挂起函数:

suspend fun suspendFun() {

}

编译成 java 代码如下:

@Nullable

public final Object suspendFun(@NotNull Continuation $completion) {

return Unit.INSTANCE;

}

可以看到挂起函数其实隐藏着一个 Continuation 协程实例参数,而这个参数其实就来源于协程体或者其他挂起函数,因此挂起函数只能在协程体内或其他函数内调用了。

suspend 修饰 lambda 表达式:

suspend {}

// 反编译结果如下

Function1 var2 = (Function1)(new Function1((Continuation)null) {

int label;

@Nullable

public final Object invokeSuspend(@NotNull Object $result) {

switch(this.label) {

case 0:

return Unit.INSTANCE;

default:

}

}

@NotNull

public final Continuation create(@NotNull Continuation completion) {

Function1 var2 = new (completion);

return var2;

}

public final Object invoke(Object var1) {

return (()this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);

}

});

suspend lambda 实际会被编译成 SuspendLambda 的子类。suspendLambda 的继承关系如下图:

image

通过反编译的代码可以发现我们在协程体内编写的代码最终是在 invokeSuspend 函数内执行的。而在 BaseContinuationImpl 内实现了 Continuation 协程接口的 resumeWidth 函数,并在其内调用了 invokeSuspend 函数。

suspend 关键字的介绍先到这里,接下来我们看协程是如何创建并运行的。

协程是如何被创建的

文件地址 kotlin.coroutines.Continuation.kt

Continuation.kt 文件基本属于协程的基础核心了,搞懂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值