大家探索Kotlin协程原理过程中总会第一时间碰见SafeCoroutine,本文就带大家深入了解一下它。
SafeCoroutine作用
SafeCoroutine在suspendCoroutine中创建,主要有两个作用。
- 保证suspendCoroutine的挂起点(也就是传入lambda的continuation参数)只会被resume一次
- 保证suspend lambda参数直接resume时,不对线程进行挂起
suspendCoroutine间接调用了suspendCoroutineUninterceptedOrReturn,通过设置传递给suspendCoroutineUninterceptedOrReturn的lambda返回值来设置当前线程是否挂起
suspendCoroutineUninterceptedOrReturn三步骤
步骤1
可以看到SafeContinuation构造器传入的是c.intercepted也就是被拦截器包裹后的Continuation,此处SafeContinuation作为代理使用的。
举个例子
此时c.intercepted()实际就是MyInterceptor,具体很简单就是获取Continuation上下文中的ContinuationInterceptor。
【注意】 result的初始值为UNDECIDED,后续会使用到它。
步骤2
此时的block其实是传入suspendCoroutine的lambda,此时可以看出使用suspendCoroutine传入的lambda的第一个参数实际上是一个SafeContinuation,当其调用resume时其实调用的是SafeContinuation的resumeWith,然后再间接调用其代理的continuation。
如果在传入suspendCoroutine的lambda中直接调用了resume(没有切换线程)
举个例子
此时直接调resume,会直接调用到SafeCoroutine的resumeWith
此时会直接调用将RESULT就会被设置成resume时传入的value,同步更新到SafeCoroutine的result字段。
步骤3
注意这个函数的返回值,这将直接决定这个线程是否会被挂起。
可以看到流程会走到这里,如果不返回COROUTINE_SUSPENDED则线程就不会被挂起。
如果当初没有在传入suspendCoroutine的lambda中直接调用了resume,而是切换了上下文则result会被设置为COROUTINE_SUSPENDED
举个例子
此时在第二步中没有调用resume,所以此时result还是初始值UNDECIDED,在第三步中会被设置为COROUTINE_SUSPENDED,此时子线程中执行resume时仍还是SafeCoroutine的resumeWith(晕的话回头再看看)
如果result被设置为COROUTINE_SUSPENDED时则会间接调用被代理的continuation。
定制suspendCoroutineUninterceptedOrReturn
所以可以看出我们可以绕过suspendCoroutine直接调用suspendCoroutineUninterceptedOrReturn拓展协程的能力了。如果直接调用suspendCoroutineUninterceptedOrReturn就可以多次重复调用resume了。
输出结果
MyInterceptorContinuation@77b989be
Start
Done
Start
Done
Start
Done
注意此时返回值为COROUTINE_SUSPENDED,否则线程不会被挂起,最终会主动进行resume,总体上就多resume 1次,就会是4对Start、Done了。