参考: 码上开学 | Bilibili Kotlin: Using Coroutines | Pluralsight 一文快速入门kotlin协程 | 掘金
Kotlin Coroutine
消除并发任务之间的协作难度,打破回调地狱,可以在同一个代码块里实现多线程切换。
launch
launch——创建一个新的协程,并在指定线程上运行它,非阻塞。
launch(Dispatchers.IO){
val image = getImage(imageId)
}
join
launch返回一个Job对象,通过Job.join()方法可以同步等待协程的完成。
fun main(args: Array<String>) = runBlocking<Unit> {
val job = scope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
}
Cancelling coroutine execution
使用 Job.cancel() 方法可以取消一个协程。
val job1 = scope.launch {...}
val job2 = scope.launch {...}
scope.cancel()
val job1 = scope.launch { … }
val job2 = scope.launch { … }
job1.cancel()
job.cancelAndJoin() 替代
job.cancel()
job.join()
Making computation code cancellable
yield()——挂起函数,定期调用来检查取消操作。
fun main(args: Array<String>) = runBlocking<Unit> {
val job = scope.launch {
repeat(1000){
print(".")
yield()
Thread.sleep(1)
}
}
delay(100)
job.cancelAndJoin()
println("done")
}
isActive——一个可通过 CoroutineScope 对象在协程内部使用的扩展属性,用于检测协程是否被取消。
fun main(args: Array<String>) = runBlocking<Unit> {
val job = scope.launch {
repeat(1000){
if(!isActive) throw CancellationException()
print(".")
Thread.sleep(1)
}
}
delay(100)
job.cancelAndJoin()
println("done")
}
fun main(args: Array<String>) = runBlocking<Unit> {
val job = scope.launch {
repeat(1000){
if(!isActive) return@launch
print(".")
Thread.sleep(1)
}
}
delay(100)
job.cancelAndJoin()
println("done")
}
CancellationException
可取消的挂起函数在取消时会抛出 CancellationException,可以用常用的方式来处理这种情况。例如,try {...} catch{...} finally {...} 。
fun main(args: Array<String>) = runBlocking<Unit> {
val job = scope.launch {
try {
repeat(1000){
yield()
print(".")
Thread.sleep(1)
}
} catch (ex:CancellationException) {
println("cancelled: ${ex.message}")
} finally {
run(NonCancellable) {
delay(1000)
println("In finally")
}
}
}
delay(100)
job.cancel(CancellationException("Reason"))
job.join()
println("done")
}
join() 和 cancelAndJoin() 两个函数都会等待所有回收操作完成后再继续执行之后的代码
Timeout
withTimeout()——函数超时返回异常
fun main(args: Array<String>) = runBlocking<Unit> {
try{
val job = withTimeout(100) {
repeat(1000){
yield()
print(".")
Thread.sleep(1)
}
}
}catch (ex:TimeoutCancellationException) {
println("cancelled: ${ex.message}")
}
delay(100)
}
withTimeoutOrNull()——函数在超时时返回Null而不是异常
fun main(args: Array<String>) = runBlocking<Unit> {
val job = withTimeoutOrNull(100) {
repeat(1000){
yield()
print(".")
Thread.sleep(1)
}
}
if(job == null) {
println("timeout")
}
}
runBlocking
runBlocking——创建一个新的协程,但是会阻塞主线程。
runBlocking{
delay(500)
}
CoroutineContext
@SinceKotlin("1.3")
public interface CoroutineContext
我们可以在每个协程块中访问CoroutineContext ,它是Element 的索引集,可以使用索引Key 来访问上下文中的元素
Job
public interface Job : CoroutineContext.Element
val job = launch {
println("isActive? ${coroutineContext[Job]!!.isActive})
}
job.join()
运行结果:isActive? true
CoroutineExceptionHandler
public interface CoroutineExceptionHandler : CoroutineContext.Element
协程的错误处理,可以自定义:
val errorHandle = CoroutineExceptionHandler { context, error ->
Log.e("Mike","coroutine error $error")
}
GlobalScope.launch(context = errorHandle) {
delay(2000)
throw Exception("test")
}
打印结果:coroutine error java.lang.Exception: test
Parent-child relationship
如果要让两个协程之间是父子关系,需要flow the coroutineContext from the outer coroutine.
val outer = launch {
launch(coroutineContext) {
repeat(1000) {
print(".")
delay(1)
}
}
}
outer.cancelAndJoin()
delay(1000)
println()
println("Outer isCancelled? ${outer.isCancelled}")
- 父协程如果取消或结束了,那么它下面的所有子协程均被取消或结束。
- 父协程需等待子协程执行完毕后才会最终进入完成状态,而不管父协程本身的代码块是否已执行完。
- 子协程被取消,父协程并不会被取消,但是子协程被取消会通知到父协程。
- 子协程会继承父协程上下文中的元素,如果自身有相同 Key 的成员,则覆盖对应 Key,覆盖效果仅在自身范围内有效。
Combining context elements
给coroutineContext定义多个元素,可以使用+ 运算符。
fun main() = runBlocking<Unit> {
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
}
打印结果:I’m working in thread DefaultDispatcher-worker-1 @test#2
newSingleThreadContext
newSingleThreadContext 用于为协程专门创建一个新的线程来运行。专用线程是非常昂贵的资源。在实际的应用程序中,它必须在不再需要时使用 close 函数释放掉,或者存储在顶级变量中以此实现在整个应用程序中重用。
newSingleThreadContext("STC").use { ctx ->
val job = launch(ctx) {
println("STC is working in thread ${Thread.currentThread().name}")
}
}
这里使用了 kotlin 标准库中的 use 函数用来在不再需要时释放 newSingleThreadContext 所创建的线程。
withContext
withContext——创建新的协程,在指定线程上运行它,执行完后自动切回。
Launch(Dispatchers.Main){
val image = withContext(Dispatchers.IO){
getImage(imageId)
}
avatarIv.setImageBitmap(image)
}
多个withContext是串行执行,消除并发代码在协作时的嵌套。
Launch(Dispatchers.Main){
withContext(Dispatchers.IO){
...
}
withContext(Dispatchers.IO){
...
}
...
}
suspend挂起函数,非阻塞式挂起。
当一个协程执行到suspend函数时,会被挂起,从当前线程脱离,执行完suspend函数后再切回来(resume)。
suspend 关键字对协程并没有直接作用,它只是一个提醒,表示该函数是个耗时操作,真正实现挂起需要函数内部直接或间接调用一个协程自带或我们自定义的挂起函数。
耗时函数被自动放在后台执行,让主线程不卡。
suspend fun suspendingGetImage(imageId:String){
withContext(Dispatchers.IO){
getImage(imageId)
}
}
launch(Dispatchers.Main){
...
val image = suspendingGetImage(imageId)
avatarIv.setImageBitmap(image)
}
async
如果同时处理多个耗时任务,且这几个任务都无相互依赖时,可以使用 async … await() 来处理。
btn.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
val time1 = System.currentTimeMillis()
val task1 = async(Dispatchers.IO) {
delay(2000)
Log.e("TAG", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one"
}
val task2 = async(Dispatchers.IO) {
delay(1000)
Log.e("TAG", "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
"two"
}
Log.e("TAG", "task1 = ${task1.await()} , task2 = ${task2.await()} , 耗时 ${System.currentTimeMillis() - time1} ms [当前线程为:${Thread.currentThread().name}]")
}
}
运行结果:
2020-03-30 16:00:20.709 : 2.执行task2… [当前线程为:DefaultDispatcher-worker-3] 2020-03-30 16:00:21.709 : 1.执行task1… [当前线程为:DefaultDispatcher-worker-3] 2020-03-30 16:00:21.711 : task1 = one , task2 = two , 耗时 2037 ms [当前线程为:main]
await() 只有在 async 未执行完成返回结果时,才会挂起协程。若 async 已经有结果了,await() 则直接获取其结果并赋值给变量,此时不会挂起协程。
val task1 = async(Dispatchers.IO) {
delay(2000)
Log.e("TAG", "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one"
}.await()
Deferred
async 函数的返回值是一个 Deferred 对象。Deferred 是一个接口类型,继承于 Job 接口,所以 Job 包含的属性和方法 Deferred 都有,其主要就是在 Job 的基础上扩展了 await() 方法。
val result: Deferred<Int> = doWorkAsync("Hello")
runBlocking {
println(result.await())
}
fun doWorkAsync(msg: String): Deferred<Int> = async {
log("$msg - Working")
delay(500)
log("$msg - Work done")
return@async 42
}
fun log(msg: String) {
println("$msg in ${Thread.currentThread().name}")
}
打印结果:
Hello - Working in ForJoinPool.commonPool-worker-1
Hello - Work done in ForJoinPool.commonPool-worker-1
42
Start lazy
CoroutineStart.LAZY 不会主动启动协程,而是直到调用async.await() 或者async.satrt() 后才会启动(即懒加载模式)
fun main() {
val time = measureTimeMillis {
runBlocking {
val asyncA = async(start = CoroutineStart.LAZY) {
delay(3000)
1
}
val asyncB = async(start = CoroutineStart.LAZY) {
delay(4000)
2
}
log(asyncA.await() + asyncB.await())
}
}
log(time)
}
[main] 3 [main] 7077
val outer = launch {
val inner = async(star = CoroutineStart.LAZY) {
delay(100)
}
}
outer.join()
使用懒加载后会形成父子协程关系。
|