day 5 knock, knock我要开始coroutine

coroutine神奇又好用,那我要怎麽开始呢?
官方提供了两种方法,launch和 async

launch

launch的意思,大概是我要这里创造新的coroutine,并在指定的Thread之中运行,这里是哪里呢?
只要是coroutinescope里面,或是suspend function都可以(因为suspend一定要在coroutineScope或suspend function里面执行)

而launch的特性是他不会在coroutine结束时返回值,多数情况都会以launch创建coroutine
start a new coroutine that is “fire and forget” — that means it won’t return the result to the caller.

val scope = CoroutineScope(rootJob)
        
scope.launch ( Dispatchers.IO ){
//code
}
//看原始码,我们写在coroutine里的代码块会被标记上suspend
fun CoroutineScope.launch {
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
}

async

那async要怎麽用?

当两个任务之间没有相互关系时,async并发可以让我们减少等待执行的时间,并回传deferred类型,因为无法确定非同步任务会在何时结束,不论是运算结果或是Exception,他都预期开发者会呼叫await()挂起函数,以获得回传值或是错误讯息,所以预设并不会丢出exception

val scope = CoroutineScope(rootJob)
scope.async {

}.await()

可以比较一下原始码

fun CoroutineScope.async() {
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
}: Deferred<T>

只看文字应该会觉得似懂非懂,这里我们再带个小范例


val task: (String) ->Unit = { Timber.d("longTimeTask in $it") }
fun longTimeTask(): (String) -> Unit {
    return task
}

val scope = CoroutineScope(rootJob)

val notRelateWork1 = scope.async {
    Timber.d("in async 1")
    longTimeTask()
}
val notRelateWork2 = scope.async {
    longTimeTask().invoke("async 2")
    Timber.d("in async 2")
}
scope.launch {
    Timber.d("in launch")
    notRelateWork1.await().invoke("async 1") //(String) -> Unit
    notRelateWork2.await() //Unit
}


//in async 1
//longTimeTask in async 2
//in async 2
//in launch
//longTimeTask in async 1

这里的顺序非常重要,尽管我们需要呼叫await,来拿到async回传参数,但在async回传值之前的代码会先被执行,而在async里面默认是回传最後一行的值,所以我搭配lambda,大家应该很容易就能看出呼叫执行task的地方差异了

重点:launch和async很大的差别在於

  1. 他们如何处理exception,async预期开发者会呼叫await来获得结果(或是exception),换句话说,他会默默地丢出exception,而只有在呼叫await你才会知道拿到result或是exeption,而launch会直接丢出来
  2. async会回传deferred< value>,而launch不回传任何东西,是fire and forget的coroutine

Warning: A big difference between launch and async is how they handle exceptions. async expects that you will eventually call await to get a result (or exception) so it won’t throw exceptions by default. That means if you use async to start a new coroutine it will silently drop exceptions.

async并发

我们介绍完了await()和async的基本知识,但刚刚提过 当两个任务之间没有相互关系时,async并发可以让我们减少等待执行的时间, 具体来说,还是看code比较实际

今天如果用launch要等两秒,但async并发只要等1秒多一点,今天如果是有10个任务,launch要等10秒,但async一样是一秒多

suspend delay1000():Int{
    delay(1000)
    //pretend a api call
    return 1
}
val time = measureTimeMillis {
    val one = async { delay1000() }
    val two = async { delay1000() }
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

//或分开写
val time = measureTimeMillis {
    println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { delay1000() }
    val two = async { delay1000() }
    one.await() + two.await()
}

我实在找不到图说明这个步骤,用文字解释就是在delay1000(),里面的delay是suspend function,在launch里面时,suspend会依顺序触发,而在async代码执行到那里可被挂起,让thread可以先执行其他任务,当delay()结束後,会切回delay後面,继续执行其他代码

延迟执行

刚才讲到了代码的执行顺序,但如果我们希望在呼叫的时候才执行,有没有像 kotlin lazy的方法呢?有,也是lazy

我觉得很清楚了,应该不用解释吧

launch lazy

launch(start = CoroutineStart.LAZY) { delay1000() }

async lazy

惰性的并发
只有通过 await 或者在 Job 的 start 函数调用的时候协程才会启动

val time = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { delay1000() }
    val two = async(start = CoroutineStart.LAZY) { delay1000() }
    // 执行一些计算
    one.start() // 启动第一个
    two.start() // 启动第二个
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

要注意,如果不用start只用await,async coroutine会依序启动,而不会变成并发启动

launch/async 後发生什麽事

当我们开启一个coroutine後,他就是 thread吗?

不对不对,我们之前讲过,kotlin 的coroutine只是一个线程框架,他是将线程包装成方便的版本,launch不是 thread,Dispatcher也不是thread,而这段Code实际上会是这样

val scope = CoroutineScope(rootJob)
        
scope.launch ( Dispatchers.IO ){
//code
}

//变成
handler.post{
   //code from launch block
   //launch里面的代码
}

launch和Dispatcher都不是thread本身,他们是将任务透过handler post到 thread 的looper里面。

参考连结:
必看
中文文档
android文档
英文async文档
github async 文档

中秋节来个应景梗图,中秋节快乐
meme

放假太爽,day 5打成day 4,回来更新


<<:  Day 05 - 行前说明 — UI 元件分类你知多少?

>>:  离职倒数11天:「怎样算好PM?」

[Day25] Rocket 序列化和反序列化以及 POST (Part 2)

那麽昨天既然讲完了 get 那麽今天就来 post 一下 为了做到後端的功能 所以本次会以 Json...

资安认知-电子邮件钓鱼

在社交工程的攻击中,我们最常见的也莫过於电子邮件钓鱼 电子邮件钓鱼也被列为主要进行社交工程演练的项目...

Day28:【技术篇】ASP DOT NET CORE 的 MVC 基础入门

一、前言   既然是全端工程师,就也带一下後端程序语言与架构上的基础内容。因为现职公司使用的是ASP...

【Day12】Latch的生成条件以及如何避免(上)

什麽是 Latch ? 前几篇在 if-else 以及 case 语句时有提到 latch,那什麽是...

[Day18] NLP会用到的模型(二)-GRU

一. LSTM的问题 LSTM虽然非常强大,但LSTM也是有一个问题,就是计算时间较久导致执行速度较...