Kotlin协程

协程github地址,Anko Coroutinesgithub地址

首先添加anko到本地项目中去,最新版本在这里:

dependencies {
    implementation "org.jetbrains.anko:anko-coroutines:$anko_version"
}

asReference()
If your asynchronous API does not support cancellation, your coroutine may be suspended for an indefinite time period. As a coroutine holds the strong references to captured objects, capturing the instance of Activity or Fragment instance may cause a memory leak.

Use asReference() in such cases instead of the direct capturing:

suspend fun getData(): Data { … }

class MyActivity : Activity() {
fun loadAndShowData() {
// Ref uses the WeakReference under the hood
val ref: Ref = this.asReference()

    async(UI) {
        val data = getData()

        // Use ref() instead of this@MyActivity
        ref().showData(data)
    }
}

fun showData(data: Data) { ... }

}
bg()
You can easily execute your code on the background thread using bg():

fun getData(): Data { … }
fun showData(data: Data) { … }

async(UI) {
val data: Deferred = bg {
// Runs in background
getData()
}

// This code is executed on the UI thread
showData(data.await())

}

1. 等待所有协程执行完毕

val jobs = List(100_000) { // create a lot of coroutines and list their jobs
    launch(CommonPool) {
        delay(1000L)
        print(".")
    }
}
jobs.forEach { it.join() } // wait for all jobs to complete,只可在suspend方法中调用join

2.已经死亡的进程,协程也go die

fun main(args: Array<String>) = runBlocking<Unit> {
    launch(CommonPool) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // just quit after delay
}
You can run and see that it prints three lines and terminates:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Active coroutines do not keep the process alive. They are like daemon threads.

3.注意取消协程后,协程是否还在运行

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch(CommonPool) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    delay(1300L) // delay a bit to ensure it was cancelled indeed
    println("main: Now I can quit.")
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

4.注意下面的例子(取消的协程仍在运行)

fun main(args: Array<String>) = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(CommonPool) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 10) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    delay(1300L) // delay a bit to see if it was cancelled....
    println("main: Now I can quit.")
}
  I'm sleeping 0 ...
  I'm sleeping 1 ...
  I'm sleeping 2 ...
  main: I'm tired of waiting!
 I'm sleeping 3 ...
 I'm sleeping 4 ...
  I'm sleeping 5 ...
  main: Now I can quit.
 I'm sleeping 6 ...
  I'm sleeping 7 ...
  I'm sleeping 8 ...
  I'm sleeping 9 ...

5.在finally中关闭资源

fun main(args: Array<String>) = runBlocking {
    val job = launch(CommonPool) {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("I'm running finally")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    delay(1300L) // delay a bit to ensure it was cancelled indeed
    println("main: Now I can quit.")
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.

6.Run non-cancellable block(重要,避免资源不能关闭)

由于协程已经被取消,任何在suspend函数的finally块中的代码都有可能挂掉 . 通常情况下,有一些操作是必须的(例如,关闭文件,取消一个任务),可以参考下述操作:

fun main(args: Array) = runBlocking {
    val job = launch(CommonPool) {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            run(NonCancellable) {
                println("I'm running finally")
                delay(1000L)
                println("And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    delay(1300L) // delay a bit to ensure it was cancelled indeed
    println("main: Now I can quit.")
}

7.Timeout

最常见的取消协程执行的原因是:协程耗时太长.Kotlin准备了withTimeout 函数来执行这项操作(注意对异常的处理) :

fun main(args: Array<String>) = runBlocking<Unit> {
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.experimental.TimeoutException: Timed out waiting for 1300 MILLISECONDS

8.Sequential by default

假设现在有两个 suspending functions :

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

如何确保他们按顺序调用-先调用 doSomethingUsefulOne,然后再调用 doSomethingUsefulTwo,最后将他们的结果值相加?

fun main(args: Array) = runBlocking {
    val time = measureTimeMillis {//注意调用的是suspend 方法
        val one = doSomethingUsefulOne()
        val two = doSomethingUsefulTwo()
        println("The answer is ${one + two}")
    }
    println("Completed in $time ms")
}
The answer is 42
Completed in 2017 ms

9.async

async 就像 launch. 不同的是 launch返回一个Job对象,不携带返回值 , 但是async 返回一个Deferred对象,可以调用 .await()获取结果 , 但是Deferred 也是一个Job, 也可以取消.

fun main(args: Array) = runBlocking {
    val time = measureTimeMillis {
        val one = async(CommonPool) { doSomethingUsefulOne() }
        val two = async(CommonPool) { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}
The answer is 42
Completed in 1017 ms

10.Lazily started async

懒加载(CoroutineStart.LAZY )async 结果,即只在需要结果时才开始运算

fun main(args: Array) = runBlocking {
    val time = measureTimeMillis {
        val one = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}
The answer is 42
Completed in 2017 ms

So, we are back to sequential execution, because we first start and await for one, and then start and await for two. It is not the intended use-case for laziness. It is designed as a replacement for the standard lazy function in cases when computation of the value involves suspending functions.

11.Async-style functions

//asyncSomethingUsefulOne返回结果是 Deferred
fun asyncSomethingUsefulOne() = async(CommonPool) {
    doSomethingUsefulOne()
}
//asyncSomethingUsefulTwo 返回结果是  Deferred
fun asyncSomethingUsefulTwo() = async(CommonPool)  {
    doSomethingUsefulTwo()
}

注意这些方法并不是suspending ,可以在任何地方调用 .但是调用它们就意味着并发,下面的例子展示了如何在协程外调用它们:

// note, that we don't have `runBlocking` to the right of `main` in this example
fun main(args: Array) {
    val time = measureTimeMillis {
        // we can initiate async actions outside of a coroutine
        val one = asyncSomethingUsefulOne()
        val two = asyncSomethingUsefulTwo()
        // but waiting for a result must involve either suspending or blocking.
        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
        runBlocking {
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}

12. 注意不同的launch对象决定了协程在何处执行

fun main(args: Array) = runBlocking {
    val jobs = arrayListOf()
    jobs += launch(Unconfined) { // not confined -- will work with main thread
        println("      'Unconfined': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine
        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent)
        println("      'CommonPool': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
        println("          'newSTC': I'm working in thread ${Thread.currentThread().name}")
    }
    jobs.forEach { it.join() }
}
       'Unconfined': I'm working in thread main
      'CommonPool': I'm working in thread ForkJoinPool.commonPool-worker-1
          'newSTC': I'm working in thread MyOwnThread
'coroutineContext': I'm working in thread main

13. Unconfined vs confined dispatcher

fun main(args: Array) = runBlocking {
    val jobs = arrayListOf()
    jobs += launch(Unconfined) { // not confined -- will work with main thread
        println("  'Unconfined': I'm working in thread ${Thread.currentThread().name}")
        delay(500)
        println(" 'Unconfined': After delay in thread ${Thread.currentThread().name}")
    }
    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine
        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")
        delay(1000)
        println("'coroutineContext': After delay in thread ${Thread.currentThread().name}")
    }
    jobs.forEach { it.join() }
}
      'Unconfined': I'm working in thread main
'coroutineContext': I'm working in thread main
      'Unconfined': After delay in thread kotlinx.coroutines.DefaultExecutor
'coroutineContext': After delay in thread main

此处非常关键:继承了coroutineContext 的runBlocking 协程在主线程中运行,unconfined 则在resume时可能切换执行线程。

14.Debugging coroutines and threads

当协程分发者为Unconfined 或multi-threaded(比如CommonPool),那么协程有可能在一个线程中暂停,在另一个线程中执行 . 即使使用一个 single-threaded 分发者,也很难确认 coroutine 正在做什么,什么时间,什么地点. 调试线程通用的做法是打印出每一个线程的名字 :

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(args: Array) = runBlocking {
    val a = async(coroutineContext) {
        log("I'm computing a piece of the answer")
        6
    }
    val b = async(coroutineContext) {
        log("I'm computing another piece of the answer")
        7
    }
    log("The answer is ${a.await() * b.await()}")
}

上面的例子中有3个协程: 协程1-runBlocking , 还有剩下的两个协程处理运算结果 . 他们都在runBlocking 的Context中执行,输出结果如下 :

[main @coroutine#2] I'm computing a piece of the answer
[main @coroutine#3] I'm computing another piece of the answer
[main @coroutine#1] The answer is 42

正如你所见,log函数用方括号打印出当前正在执行逻辑的线程,即主线程,如果调试模式打开会显示出几号(@coroutine#2)。

15.Jumping between threads

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(args: Array<String>) {
    val ctx1 = newSingleThreadContext("Ctx1")
    val ctx2 = newSingleThreadContext("Ctx2")
    runBlocking(ctx1) {
        log("Started in ctx1")
        run(ctx2) {
            log("Working in ctx2")
        }
        log("Back to ctx1")
    }
}

上面演示了2中新技术: 第一个是 runBlocking 传入一个明确指定的context,第二个是调用run去在当前协程中改变协程的context,结果如下:
[Ctx1 @coroutine#1] Started in ctx1
[Ctx2 @coroutine#1] Working in ctx2
[Ctx1 @coroutine#1] Back to ctx1

16.Job in the context

The coroutine Job is part of its context. The coroutine can retrieve it from its own context using coroutineContext[Job]expression:

fun main(args: Array<String>) = runBlocking {
    println("My job is ${coroutineContext[Job]}")
}

My job is BlockingCoroutine{Active}@65ae6ba4

So, isActive in CoroutineScope is just a convenient shortcut for coroutineContext[Job]!!.isActive.

17. Children of a coroutine

当协程的上下文(coroutineContext )用来启动一个新的协程,新协程就是当前协程的子协程 . 当父协程被取消,它所有的子协程也都会被取消 .

fun main(args: Array) = runBlocking {
    // start a coroutine to process some kind of incoming request
    val request = launch(CommonPool) {
        // 它有两个子Job,一个有单独的context 
        val job1 = launch(CommonPool) {
            println("job1: I have my own context and execute independently!")
            delay(1000)
            println("job1: I am not affected by cancellation of the request")
        }
        // 另一个采用父context 
        val job2 = launch(coroutineContext) {
            println("job2: I am a child of the request coroutine")
            delay(1000)
            println("job2: I will not execute this line if my parent request is cancelled")
        }
        // request completes when both its sub-jobs complete:
        job1.join()
        job2.join()
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}
result:
job1: I have my own context and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?

18.Combining contexts

不同的Coroutine context可以用+连接在一起 .+号右边的的Context替换左边的Context .比如,一个Job的父协程可以被继承 , 但是分发者会被替换(注意他们的Context并不相等) :

fun main(args: Array) = runBlocking {
    // start a coroutine to process some kind of incoming request
    val request = launch(coroutineContext) { // use the context of `runBlocking`
        //在CommonPool 中新建一个需要CPU密集操作的子job  !!! 
        val job = launch(coroutineContext + CommonPool) {
            println("job: I am a child of the request coroutine, but with a different dispatcher")
            delay(1000)
            println("job: I will not execute this line if my parent request is cancelled")
        }
        job.join() // request completes when its sub-job completes
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}
job: I am a child of the request coroutine, but with a different dispatcher
main: Who has survived request cancellation?

19.给协程起一个名字方便调试

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(args: Array<String>) = runBlocking(CoroutineName("main")) {
    log("Started main coroutine")
    // run two background value computations
    val v1 = async(CommonPool + CoroutineName("v1coroutine")) {
        log("Computing v1")
        delay(500)
        252
    }
    val v2 = async(CommonPool + CoroutineName("v2coroutine")) {
        log("Computing v2")
        delay(1000)
        6
    }
    log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
}
[main @main#1] Started main coroutine
[ForkJoinPool.commonPool-worker-1 @v1coroutine#2] Computing v1
[ForkJoinPool.commonPool-worker-2 @v2coroutine#3] Computing v2
[main @main#1] The answer for v1 / v2 = 42

20. 通过一个明确指定的Job取消所有Job

针对内存泄露的情况,可以这样做

fun main(args: Array) = runBlocking {
    val job = Job() // create a job object to manage our lifecycle
    // now launch ten coroutines for a demo, each working for a different time
    val coroutines = List(10) { i ->
        // they are all children of our job object
        launch(coroutineContext + job) { // we use the context of main runBlocking thread, but with our own job object
            delay(i * 200L) // variable delay 0ms, 200ms, 400ms, ... etc
            println("Coroutine $i is done")
        }
    }
    println("Launched ${coroutines.size} coroutines")
    delay(500L) // delay for half a second
    println("Cancelling job!")
    job.cancel() // cancel our job.. !!!(在Activity结束时调用)
    delay(1000L) // delay for more to see if our coroutines are still working
}
Launched 10 coroutines
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Cancelling job!

21.Channels

Deferred 可以在不同协程中传递数据 ,但是, Channels可以传送一系列数据

21.1 Channel基础用法

fun main(args: Array<String>) = runBlocking {
    val channel = Channel()
    launch(CommonPool) {
        // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
        for (x in 1..5) channel.send(x * x)
    }
    // here we print five received integers:
    repeat(5) { println(channel.receive()) }
    println("Done!")
}
1
4
9
16
25
Done!

21.2 关闭和遍历channels

当没有更多数据传送时,channel 可以被关闭 .此时, 在接收方可以很方便地使用一个传统的循环去接收数据 .
从概念上讲,关闭操作就像发送一个关闭标记 . 接收者一旦收到这个标记便会停止,但是有一点会得到保证:接收者在收到关闭标记之前,所有的数据都已经被接收 :

fun main(args: Array<String>) = runBlocking {
    val channel = Channel()
    launch(CommonPool) {
        for (x in 1..5) channel.send(x * x)
        channel.close() // we're done sending
    }
    // here we print received values using `for` loop (until the channel is closed)
    for (y in channel) println(y)
    println("Done!")
}

21.3 Building channel producers(生产者)
生产者一方可以方便调用produce,它有一个consumeEach方法供消费方调用,这个可以取代消费方的循环操作

fun produceSquares() = produce(CommonPool) {
    for (x in 1..5) send(x * x)
}
fun main(args: Array) = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
    println("Done!")
}

22. Pipelines

Pipeline是这样一种模式: 生产者生产连续不断的数据,或一系列数据 :

fun produceNumbers() = produce(CommonPool) {
    var x = 1
    while (true) send(x++) //发送无限的Integers  }

另外一些协程或 coroutines 消费这个流,做一些处理,并生产一些结果 .下面的例子是生产出他们的平方 :

fun square(numbers: ReceiveChannel<Int>) = produce<Int>(CommonPool) {
    for (x in numbers) send(x * x)
}

The main code starts and connects the whole pipeline:

fun main(args: Array) = runBlocking {
    val numbers = produceNumbers() // produces integers from 1 and on
    val squares = square(numbers) // squares integers
    for (i in 1..5) println(squares.receive()) // print first five
    println("Done!") // we are done
    squares.cancel() //(在Android中尤其注意要关闭) need to cancel these coroutines in a larger app
    numbers.cancel()
}

23. 同步问题

suspend fun massiveRun(context: CoroutineContext, action: suspend () -> Unit) {
    val n = 1000 // number of coroutines to launch
    val k = 1000 // times an action is repeated by each coroutine
    val time = measureTimeMillis {
        val jobs = List(n) {
            launch(context) {
                repeat(k) { action() }
            }
        }
        jobs.forEach { it.join() }
    }
    println("Completed ${n * k} actions in $time ms")    
}

var counter = 0
fun main(args: Array) = runBlocking<Unit> {
    massiveRun(CommonPool) {
        counter++
    }
    println("Counter = $counter")
}

最后不太可能输出 “Counter = 1000000”,因为1k个协程没有同步地增加counter
解决:

var counter = AtomicInteger()
fun main(args: Array<String>) = runBlocking {
    massiveRun(CommonPool) {
        counter.incrementAndGet()
    }
    println("Counter = ${counter.get()}")
}
val counterContext = newSingleThreadContext("CounterContext")
var counter = 0
fun main(args: Array<String>) = runBlocking {
    massiveRun(counterContext) { // run each coroutine in the single-threaded context
        counter++
    }
    println("Counter = $counter")
}
val mutex = Mutex()
var counter = 0
fun main(args: Array<String>) = runBlocking {
    massiveRun(CommonPool) {
        mutex.lock()
        try { counter++ }
        finally { mutex.unlock() }
    }
    println("Counter = $counter")
}

24. Select expression

Select表达式使得同一时间选择最新的 await结果变为可能 。
现在有两个生产String类型的生产者 : fizz and buzz.fizz 生产者每300ms生产 “Fizz”字符串 :

fun fizz(context: CoroutineContext) = produce<String>(context) {
    while (true) { // sends "Fizz" every 300 ms
        delay(300)
        send("Fizz")
    }
}

buzz生产者每隔500ms生产 “Buzz!” 字符串:

fun buzz(context: CoroutineContext) = produce<String>(context) {
    while (true) { // sends "Buzz!" every 500 ms
        delay(500)
        send("Buzz!")
    }
}

使用 suspend函数可以接收从一个channel或其他channel的发出的数据 . select 表达式可以使用onReceive 表达式来同步接收 ,选择最新的:

suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
    select<Unit> { //  表示此处不生产任何结果 
        fizz.onReceive { value ->  // this is the first select clause
            println("fizz -> '$value'")
        }
        buzz.onReceive { value ->  // this is the second select clause
            println("buzz -> '$value'")
        }
    }
}

运行7次,看下结果:

fun main(args: Array) = runBlocking {
    val fizz = fizz(coroutineContext)
    val buzz = buzz(coroutineContext)
    repeat(7) {
        selectFizzBuzz(fizz, buzz)
    }
}

The result of this code is:

fizz -> 'Fizz' 300ms
buzz -> 'Buzz!' 500ms
fizz -> 'Fizz' 300ms
fizz -> 'Fizz' 300ms
buzz -> 'Buzz!' 500ms
fizz -> 'Fizz'
buzz -> 'Buzz!'

Selecting on close
onReceiveOrNull表达式用于一种特殊的场合:channel已关闭 .
suspend fun selectAorB(a: ReceiveChannel, b: ReceiveChannel): String =
select {
a.onReceiveOrNull { value ->
if (value == null)
“Channel ‘a’ is closed”
else
“a -> ‘value’”  
        }  
        b.onReceiveOrNull { value ->   
            if (value == null)   
                “Channel ‘b’ is closed”  
            else  
                “b -> ‘
value’”          }          b.onReceiveOrNull { value ->               if (value == null)                   “Channel ‘b’ is closed”              else                  “b -> ‘
value’”
}
}

fun main(args: Array) = runBlocking {
// we are using the context of the main thread in this example for predictability …
val a = produce(coroutineContext) {
repeat(4) { send(“Hello it”) }  
    }  
    val b = produce(coroutineContext) {  
        repeat(4) { send(“World
it”) }      }      val b = produce(coroutineContext) {          repeat(4) { send(“World
it”) }
}
repeat(8) { // print first eight results
println(selectAorB(a, b))
}
}

a -> ‘Hello 0’
a -> ‘Hello 1’
b -> ‘World 0’
a -> ‘Hello 2’
a -> ‘Hello 3’
b -> ‘World 1’
Channel ‘a’ is closed
Channel ‘a’ is closed

注意:
select更倾向于选择第一个条件 . 然而,由于我们使用的是非缓冲channel,其也会给b机会发送数据。
Selecting to send
Select 表达式有 onSend声明,可以用它来发送自己想要的数据 .
Let us write an example of producer of integers that sends its values to a side channel when the consumers on its primary channel cannot keep up with it:
fun produceNumbers(side: SendChannel) = produce(CommonPool) {
for (num in 1..10) { // produce 10 numbers from 1 to 10
delay(100) // every 100 ms
select {
onSend(num) {} // Send to the primary channel
side.onSend(num) {} // or to the side channel
}
}
}

Consumer is going to be quite slow, taking 250 ms to process each number:
fun main(args: Array) = runBlocking {
val side = Channel() // allocate side channel
launch(coroutineContext) { // this is a very fast consumer for the side channel
side.consumeEach { println(“Side channel has it”) }  
    }  
    produceNumbers(side).consumeEach {   
        println(“Consuming
it”) }      }      produceNumbers(side).consumeEach {           println(“Consuming
it”)
delay(250) // let us digest the consumed number properly, do not hurry
}
println(“Done consuming”)
}

Consuming 1
Side channel has 2
Side channel has 3
Consuming 4
Side channel has 5
Side channel has 6
Consuming 7
Side channel has 8
Side channel has 9
Consuming 10
Done consuming

Selecting deferred values
Deferred values can be selected using onAwait clause. 首先定义一个异步函数:经过随机等待时间后返回一个String:
fun asyncString(time: Int) = async(CommonPool) {
delay(time.toLong())
“Waited for $time ms”
}

接下来启动12个线程(时间随机)
fun asyncStringsList(): List

你可能感兴趣的:(Android基础知识)