开启生长之旅!这是我参与「日新方案 12 月更文挑战」的第10天,点击查看活动概况

1.launch发动协程

fun main() = runBlocking {
    launch { 
        delay(1000L) 
        println("World!") 
    }
    println("Hello") 
}
fun main() {
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
    Thread.sleep(2000L)
}
//输出成果
//Hello
//World!

上面是两段代码,这两段代码都是经过launch发动了一个协程而且输出成果也是相同的。

榜首段代码中的runBlocking是协程的另一种发动办法,这儿先看第二段代码中的launch的发动办法;

  • GlobalScope.launch

GlobalScope.launch是一个扩展函数,接收者是CoroutineScope,意思便是协程作用域,这儿的launch等价于CoroutineScope的成员办法,假如要调用launch来发动一个协程就必须先拿到CoroutineScope对象。GlobalScope.launch源码如下

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

里边有三个参数:

  • context: 意思是上下文,默认是EmptyCoroutineContext,有默认值就能够不传,可是也能够传递Kotlin提供的Dispatchers来指定协程运转在哪一个线程中;
  • start: CoroutineStart代表了协程的发动形式,不传则默认运用DEFAULT(依据上下文立即调度协程履行),除DEFAULT外还有其他类型:
    • LAZY:推迟发动协程,只在需求时才发动。
    • ATOMIC:以一种不可撤销的办法,依据其上下文组织履行的协程;
    • UNDISPATCHED:立即履行协程,直到它在当时线程中的榜首个挂起点;
  • block: suspend是挂起的意思,CoroutineScope.()是一个扩展函数,Unit是一个函数类似于Javavoid,那么suspend CoroutineScope.() -> Unit就能够这么了解了:首要,它是一个挂起函数,然后它还是CoroutineScope类的成员或许扩展函数,参数为空,回来值类型为Unit
  • delay(): delay()办法从字面了解便是推迟的意思,在上面的代码中推迟了1秒再履行World,从源码能够看出来它跟其他办法不相同,多了一个suspend关键字
//      挂起
//       ↓
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

suspend的意思便是挂起,被它修饰的函数便是挂起函数, 这也就意味着delay()办法具有挂起和康复的能力;

  • Thread.sleep(2000L)

这个是休眠2秒,那么这儿为什么要有这个呢?要解答这疑问其实不难,将Thread.sleep(2000L)删去后在运转代码能够发现只打印了Hello然后程序就完毕了,World!并没有被打印出来。

为什么? 将上面的代码转换成线程实现如下:

fun main() {
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("Hello World!")
    }
}

假如不增加isDaemon = true成果输出正常,假如加了那么就没有成果输出。isDaemon的加入后其实是创建了一个【看护线程】,这就意味着主线程完毕的时候它会跟着被销毁,所以对于将Thread.sleep删去后导致GlobalScope创建的协程不能正常运转的主要原因便是经过launch创建的协程还没开端履行程序就完毕了。那么Thread.sleep(2000L)的作用便是为了不让主线程退出。

别的这儿还有一点需求注意:程序的履行过程并不是依照次序履行的。

fun main() {
    GlobalScope.launch {                // 1
        println("Launch started!")      // 2
        delay(1000L)                    // 3
        println("World!")         	   // 4
    }
    println("Hello")            		// 5
    Thread.sleep(2000L)                 // 6
    println("Process end!")             // 7
}
/*
输出成果:
Hello
Launch started!
World!
Process end!
*/

上面的代码履行次序是1、5、6、2、3、4、7,这个其实好了解,首要履行1,然后再履行5,履行6的时候等待2秒,在这个等待过程中协程创建完毕了开端履行2、3、4都能够履行了,当2、3、4履行完毕后等待6履行完毕,最后履行7,程序完毕。

2.runBlocking发动协程

fun main() {
    runBlocking {                // 1
        println("launch started!")      // 2
        delay(1000L)           // 3
        println("World!")         	    // 4
    }
    println("Hello")            		// 5
    Thread.sleep(2000L)           // 6
    println("Process end!")             // 7
}

上面这段代码只是将GlobalScope.launch改成了runBlocking,可是履行次序却彻底不相同,它的履行顺讯为代码次序1~7,这是由于runBlocking是带有堵塞特点的,它会堵塞当时线程的履行。这是它跟launch的最大差异。

runBlockinglanuch的别的一个差异是GlobalScope,从代码中能够看出runBlocking并不需求这个,这点能够从源码中剖析

public actual fun <T> runBlocking(
    context: CoroutineContext, 
    block: suspend CoroutineScope.() -> T): T {
    ...
}

顶层函数:类似于Java中的静态函数,在Java中常用与东西类,例如StringUtils.lastElement();

runBlocking是一个顶层函数,因而能够直接运用它;在它的第二个参数block中有一个回来值类型:T,它刚好跟runBlocking的回来值类型是相同的,因而能够推测出runBlocking是能够有回来值的

fun main() {
    val result = test(1)
    println("result:$result")
}
fun test(num: Int) = runBlocking {
    return@runBlocking num.toString()
}
//输出成果:
//result:1

可是,Kotlin在文档中注明了这个函数不应该从协程中运用。它的设计目的是将常规的堵塞代码与以挂起风格编写的库连接起来,以便在主函数和测验中运用。 因而在正式环境中这种办法最好不必。

3.async发动协程

在 Kotlin 当中,能够运用 async{} 创建协程,而且还能经过它回来的句柄拿到协程的履行成果。

fun main() = runBlocking {
    val deferred = async {
        1 + 1
    }
    println("result:${deferred.await()}")
}
//输出成果:
//result:2

上面的代码发动了两个协程,发动办法是runBlockingasync,由于async的调用需求一个作用域,而runBlocking恰好满意这个条件,GlobalScope.launch也能够满意这个条件可是GlobalScope也不主张在生产环境中运用,由于GlobalScope 创建的协程没有父协程,GlobalScope 通常也不与任何生命周期组件绑定。除非手动办理,不然很难满意我们实际开发中的需求。

上面的代码多了一个deferred.await()它便是获取最终成果的关键。

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

asynclaunch相同也是一个扩展函数,也有三个参数,和launch的区别在于两点:

  • block的函数类型: launch回来的是Unit类型,async回来的是泛型T
  • 回来值不同: launch回来的是Jobasync回来的是Deffered<T>,而async能够回来履行成果的关键就在这儿。

发动协程的三种办法都讲完了,这儿存在一个疑问,launchasync都有回来值,为什么async能够获取履行成果,launch却不行?

这主要跟launch的回来值有关,launch的回来值Job代表的是协程的句柄,而句柄并不能回来协程的履行成果。

句柄: 句柄指的是中心前言,经过这个中心前言能够操控、操作某样东西。举个例子,door handle 是指门把手,经过门把手能够去操控门,但 door handle 并非 door 自身,只是一个中心前言。又比如 knife handle 是刀柄,经过刀柄能够运用刀。

协程的三中发动办法区别如下:

  • launch:无法获取履行成果,回来类型Job,不会堵塞;
  • async:可获取履行成果,回来类型Deferred,调用await()会堵塞不调用则不会但也无法获取履行成果;
  • runBlocking:可获取履行成果,堵塞当时线程的履行,多用于Demo、测验,官方引荐只用于连接线程与协程。

【Kotlin回顾】10.如何启动协程