本文我们来聊聊协程是如何实现切换线程的。要搞清楚这个问题,我觉得需要搞懂这几个知识点:
-
Continuation,简单讲可以把它看成是Callback,回调。当协程调用suspend函数,协程会被挂起,当suspend函数执行完成后,会通过Continuation的resumeWith方法,将执行结果返回给协程让协程继续执行。
-
ContinuationInterceptor顾名思义是Continuation拦截器,也就是Callback拦截器,它的作用就是让回调在指定的线程中执行。假设有这样一个场景,在主线程中开启协程,在子线程中执行耗时操作,当耗时操作执行完毕时,它需要执行回调方法,而回调是需要在主线程中执行的,协程框架内部在协程开启的时候就会通过拦截器将回调与主线程绑定在一起,让回调一定在主线程中执行
-
CoroutineDispatcher是拦截器的子类,除了拥有拦截器的功能之外,它还有两个重要作用,isDispatchNeeded(context:CoroutineContext)决定回调是否需要分发到其它线程中执行, dispatch(context: CoroutineContext, block: Runnable)将回调分发到指定线程中执行
-
ThreadContextElement处理协程中的线程的ThreadLocal相关的变量。ThreadLocal很好理解,就是线程私有变量,只能被当前线程访问。那么协程中为什么会有这ThreadLocal呢?举个例子,在同一个线程中执行两个协程,将协程命名为A、B,在执行协程时打印出协程的名称。因为是同一个线程,而且协程的名称是存储在ThreadLocal中的。所以在协程执行的时候,需要将ThreadLocal中保存协程名称的变量修改为当前协程的名称,协程执行完毕时,将变量重置。
首先通过一个例子来讲解Continuation
fun main() = runBlocking(Dispatchers.Main) { // 花括号中是Continuation
suspendNoChangeThread()
suspendChangeToIOThread()
normalFunc()
}
fun normalFunc() {
// do something
}
suspend fun suspendNoChangeThread() {
suspendCoroutine<Unit> {
it.resume(Unit)
}
}
suspend fun suspendChangeToIOThread(): String {
return withContext(Dispatchers.IO) {
Thread.sleep(1000)
return@withContext &#