Kotlin 协程

为什么需要协程

在JavaScript ES6中引入了Promise对象,以及async和await关键字,使得异步编程变得容易了:

const child_process = require('child_process');
const worker = require('worker_threads');

function getData() {
    return new Promise((resolve, reject) => {
        // 这里开启了一个新的工作线程,工作耗时2s
        const th = new worker.Worker(__filename);
        th.once('message', data => {
            resolve(data);
        })
    });
}

async function asyncTask() {
    var data = await getData();
    console.log('主线程', worker.threadId, ': 很明显,工作线程计算完毕了,结果是多少呢,我已经返回给调用者了');
    return data
}

function main() {
    console.log('主线程', worker.threadId, ': 开始你的表演show');
    asyncTask().then(data => {
        console.log('主线程', worker.threadId, ': 我当然知道结果是', data);
    });
    console.log('主线程', worker.threadId, ': 你慢慢来,别着急哈!');
}

if (worker.isMainThread) {
    main();
} else {
    console.log('工作线程', worker.threadId, ': 开始堵塞');
    child_process.execSync(`sleep 2`);
    console.log('工作线程', worker.threadId, ': 我好了');
    worker.parentPort.postMessage(2020)
}

/*
主线程 0 : 开始你的表演show
主线程 0 : 你慢慢来,别着急哈!
工作线程 1 : 开始堵塞
主线程 0 : 很明显,工作线程计算完毕了,结果是多少呢,我已经返回给调用者了
主线程 0 : 我当然知道结果是 2020
工作线程 1 : 我好了
*/

这种编程方式是消息驱动的,在主线程中我们可以非常方便地和子线程通信,于是协程诞生了。

Kotlin协程解决了什么问题?

协程是通过对调用堆栈的复制,使得当前方法可以被暂停,并且之后可以在其它线程上恢复的一种魔法。它能使异步编程变得容易。
这些暂停是调用协程API时执行的,比如withContext()切换执行线程,当前线程上的协程会被remove,然后被标记为暂停状态存放起来,直到withContext()结束,resume状态改变,并加载到新的线程。

fun main() {
    val a = GlobalScope.async(Dispatchers.IO) {
        Thread.sleep(8000)
        88
    }
    val b = GlobalScope.async {
        Thread.sleep(4000)
        2
    }
    GlobalScope.launch {
        val st = System.currentTimeMillis()
        log.d("a = ", a.await())
        log.d("b = ", b.await())
        log.d("a + b = ", a.await() + b.await())
        log.d("耗时", System.currentTimeMillis() - st)
    }
    repeat(800) {
        Thread.sleep(100)
    }
}

/** 8s后:
Logger@Logger ~ Coroutine.kt(37) ~ test.CoroutineKt$main$1 # invokeSuspend() DefaultDispatcher-worker-2: a =  88
Logger@Logger ~ Coroutine.kt(38) ~ test.CoroutineKt$main$1 # invokeSuspend() DefaultDispatcher-worker-2: b =  2
Logger@Logger ~ Coroutine.kt(39) ~ test.CoroutineKt$main$1 # invokeSuspend() DefaultDispatcher-worker-2: a + b =  90
Logger@Logger ~ Coroutine.kt(40) ~ test.CoroutineKt$main$1 # invokeSuspend() DefaultDispatcher-worker-2: 耗时 8043
*/

概念

  • 协程被”暂停“
    是指当前协程需要等待后台任务完成,因此暂时从当前线程移除,之后再恢复到其它线程。

  • launch() VS async()
    首先,async有一个好兄弟:await,可以访问async的返回值,而launch被忘却(fire and forget)。
    其次,异常的处理方式不同:launch重新抛出异常,而async控制住异常,直到await被调用。

  • 协程范围 Scope
    范围就是一种生命周期。
    GlobalScope:全局范围,用于启动在整个应用程序生命周期内运行且不会过早取消的顶级协程。

fun main() {
    GlobalScope.launch(Dispatchers.Unconfined) { // 在主线程上开启协程
        log.d("开启协程")
    }
}
  • 协程上下文

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html

Default    CPU密集任务,它使用一个共享后台线程的公共池。
IO         IO任务,使用按需创建的线程共享池。
Unconfined 在当前调用框架中开始协程执行,直到第一次挂起,协程生成器函数随后返回。协程程序稍后将在相应的挂起函数使用的任何线程中恢复,而不会将其限制在任何特定的线程或池中。 该Unconfined调度程序通常不应在代码中使用。
Main       安卓等平台的UI线程。

可以使用newSingleThreadContext和newFixedThreadPoolContext创建专用线程池。
可以使用asCoroutineDispatcher扩展功能将任意Executor转换为调度程序。

文档

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/index.html

原文地址:https://www.cnblogs.com/develon/p/12743779.html