Kotlin-超詳細(xì)協(xié)程簡(jiǎn)單易懂

協(xié)程通過(guò)替代回調(diào)(callback)來(lái)簡(jiǎn)化異步代碼。

協(xié)程的執(zhí)行其實(shí)是斷斷續(xù)續(xù)的: 執(zhí)行一段, 掛起來(lái), 再執(zhí)行一段, 再掛起來(lái), ...
每個(gè)掛起的地方是一個(gè)suspension point, 每一小段執(zhí)行是一個(gè)Continuation。
協(xié)程的執(zhí)行流被它的 "suspension point" 分割成了很多個(gè) "Continuation" 。

官方示例

runBlocking {
            repeat(100000) {
                //循環(huán)100000次
                launch {
                    //開(kāi)啟一個(gè)協(xié)程
                    delay(1000L)
                    Log.i("log", "aaa")
                }
            }
        }

delay 是一個(gè)特殊的 掛起函數(shù) ,它不會(huì)造成線程阻塞,但是會(huì)掛起協(xié)程,并且只能在協(xié)程中使用。當(dāng)這個(gè)協(xié)程處于等待狀態(tài)時(shí)該線程會(huì)返回線程池中,當(dāng)?shù)却Y(jié)束的時(shí)候,這個(gè)協(xié)程會(huì)在線程池中的空閑線程上恢復(fù)。

runBlocking并非coroutine-builder,所以它不需要CoroutineScope來(lái)調(diào)用,僅僅是能夠接收一個(gè)suspend lambda而已。

launch和cancle

 val job = GlobalScope.launch(Dispatchers.Default) {
        //推薦使用默認(rèn)的Dispatchers.Default上下文
        //在一個(gè)協(xié)程環(huán)境中,執(zhí)行后臺(tái)耗時(shí)操作
      repeat(100000) {
            delay(1000L)// delay是一個(gè)特殊的掛起函數(shù) ,它不會(huì)造成線程阻塞,但是會(huì)掛起協(xié)程,并且只能在協(xié)程中使用。
            Log.i("log", "aaa")
      }
}
Log.i("log", "執(zhí)行主線程代碼")//這句代碼會(huì)立即執(zhí)行,因?yàn)閐elay不會(huì)阻塞主線程
tv.setOnClickListener {
  job.cancel()//協(xié)程中,對(duì)這個(gè)任務(wù)進(jìn)行取消
}

async和await

GlobalScope.launch(Dispatchers.Default) {
     val result = async {
           netRequest()
     }
     Log.i("log", "${result.await()}")//在async方法結(jié)束的時(shí)候,就會(huì)調(diào)用await()方法
}

fun netRequest(): Int {
    return 666
}

runBlocking執(zhí)行塊

fun play() = runBlocking {
    GlobalScope.launch {
        // 在后臺(tái)啟動(dòng)一個(gè)新的協(xié)程并繼續(xù)
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!") // 主協(xié)程在這里會(huì)立即執(zhí)行
    delay(2000L)      // 延遲 2 秒來(lái)保證 JVM 存活
}

輸出結(jié)果 Hello! World!

delay和runBlocking的區(qū)別:
delay:非阻塞的函數(shù)
runBlocking:會(huì)一直阻塞到塊中的代碼執(zhí)行完

join

fun play() = runBlocking {
    val job = GlobalScope.launch {
        // 在后臺(tái)啟動(dòng)一個(gè)新的協(xié)程并繼續(xù)
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!")
    job.join()//等待直到子協(xié)程執(zhí)行結(jié)束  主協(xié)程與后臺(tái)作業(yè)的持續(xù)時(shí)間沒(méi)有任何關(guān)系了
    Log.i("log", "子協(xié)程執(zhí)行結(jié)束")
}

輸出結(jié)果:Hello! World! 子協(xié)程執(zhí)行結(jié)束

結(jié)構(gòu)化的并發(fā):我們可以在執(zhí)行操作所在的指定作用域內(nèi)啟動(dòng)協(xié)程, 而不是像通常使用線程(線程總是全局的)那樣在 GlobalScope 中啟動(dòng)。

fun play() = runBlocking {
    launch {
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello,")
}

作用域構(gòu)建器

fun play() = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        Log.i("log", "Task from runBlocking")
    }

    coroutineScope { // 創(chuàng)建一個(gè)協(xié)程作用域,等待所有子協(xié)程執(zhí)行完畢時(shí)不會(huì)阻塞當(dāng)前線程,并且在所有已啟動(dòng)子協(xié)程執(zhí)行完畢之前不會(huì)結(jié)束。r
        launch {
            delay(500L)
            Log.i("log", "Task from nested launch")
        }

        delay(100L)
        Log.i("log", "Task from coroutine scope") // 這一行會(huì)在內(nèi)嵌launch之前輸出
    }
    Log.i("log", "Coroutine scope is over")// 這一行在內(nèi)嵌launch執(zhí)行完畢后才輸出
}

輸出結(jié)果:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

掛起函數(shù):使用關(guān)鍵字suspend

fun play() = runBlocking{
    launch {
        downLoad()
    }
    Log.i("log", "Hello,")
}

suspend fun downLoad(){
    Log.i("log", "World")
}

suspend方法只被允許在協(xié)程或另一個(gè)掛起函數(shù)中調(diào)用, 不能在協(xié)程外面調(diào)用。
suspend方法本質(zhì)上, 與普通方法有較大的區(qū)別, suspend方法的本質(zhì)是異步返回,在協(xié)程里面, 調(diào)用suspend方法, 異步的數(shù)據(jù)像同步一樣般return了。

調(diào)用suspend方法的詳細(xì)流程是:
在協(xié)程里, 如果調(diào)用了一個(gè)suspend方法, 協(xié)程就會(huì)掛起, 釋放自己的執(zhí)行權(quán), 但在協(xié)程掛起之前, suspend方法內(nèi)部一般會(huì)啟動(dòng)了另一個(gè)線程或協(xié)程, 我們暫且稱之為"分支執(zhí)行流"吧, 它的目的是運(yùn)算得到一個(gè)數(shù)據(jù)。
當(dāng)suspend方法里的“分支執(zhí)行流”完成后, 就會(huì)調(diào)用系統(tǒng)API重新恢復(fù)協(xié)程的執(zhí)行, 同時(shí)會(huì)數(shù)據(jù)返回給協(xié)程(如果有的話)。
在沒(méi)有協(xié)程的世界里, 通常異步的方法都需要接受一個(gè)callback用于發(fā)布運(yùn)算結(jié)果。在協(xié)程里, 所有接受callback的方法, 都可以轉(zhuǎn)成不需要callback的suspend方法。
suspend方法并不總是引起協(xié)程掛起, 只有其內(nèi)部的數(shù)據(jù)未準(zhǔn)備好時(shí)才會(huì)。
需要注意的是: await是suspend方法, 但async不是, 所以它才可以在協(xié)程外面調(diào)用, async只是啟動(dòng)了協(xié)程, async本身不會(huì)引起協(xié)程掛起, 傳給async的lambda(也就是協(xié)程體)才可能引起協(xié)程掛起。

取消
協(xié)程的取消是 協(xié)作 的。一段協(xié)程代碼必須協(xié)作才能被取消。 所有 kotlinx.coroutines 中的掛起函數(shù)都是 可被取消的 。它們檢查協(xié)程的取消, 并在取消時(shí)拋出 CancellationException。 然而,如果協(xié)程正在執(zhí)行計(jì)算任務(wù),并且沒(méi)有檢查取消的話,那么它是不能被取消的。

runBlocking {
    val startTime = System.currentTimeMillis()

    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 50) { // 一個(gè)執(zhí)行計(jì)算的循環(huán),只是為了占用 CPU
            // 每秒打印消息兩次
            if (System.currentTimeMillis() >= nextPrintTime) {
                Log.i("log", "job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 等待一段時(shí)間
    Log.i("log", "main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消一個(gè)作業(yè)并且等待它結(jié)束
    Log.i("log", "main: Now I can quit.")
}

在調(diào)用取消后, 作業(yè)仍然運(yùn)行到了結(jié)束為止。

使計(jì)算代碼可取消

runBlocking {
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        //isActive:當(dāng)當(dāng)前作業(yè)仍處于活動(dòng)狀態(tài)(尚未完成且尚未取消)時(shí),返回true
        while (isActive) { //使用isActive,可以被取消的計(jì)算循環(huán)
            if (System.currentTimeMillis() >= nextPrintTime) {
                Log.i("log", "job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L)// delay a bit
    Log.i("log", "main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    Log.i("log", "main: Now I can quit")
}

輸出結(jié)果:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
I'm tired of waiting!
Now I can quit

在finally中釋放資源

val job = launch {
    try {
        repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } finally {
        println("job: I'm running finally")
    }
}
delay(1300L) // 延遲一段時(shí)間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業(yè)并且等待它結(jié)束
println("main: Now I can quit.")

運(yùn)行不能取消的代碼塊

任何嘗試在 finally 塊中調(diào)用掛起函數(shù)的行為都會(huì)拋出 CancellationException,因?yàn)檫@里持續(xù)運(yùn)行的代碼是可以被取消的。通常,這并不是一個(gè)問(wèn)題,所有良好的關(guān)閉操作(關(guān)閉一個(gè)文件、取消一個(gè)作業(yè)、或是關(guān)閉任何一種通信通道)通常都是非阻塞的,并且不會(huì)調(diào)用任何掛起函數(shù)。然而,在真實(shí)的案例中,當(dāng)你需要掛起一個(gè)被取消的協(xié)程,你可以將相應(yīng)的代碼包裝在 withContext(NonCancellable) {……} 中,并使用 withContext

val job = launch {
    try {
        repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } finally {
        withContext(NonCancellable) {
            println("job: I'm running finally")
            delay(1000L)//如果不用NonCancellable,這個(gè)delay不會(huì)執(zhí)行
            println("job: And I've just delayed for 1 sec because I'm non-cancellable")
        }
    }
}
delay(1300L) // 延遲一段時(shí)間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業(yè)并等待它結(jié)束
println("main: Now I can quit.")

使用 withTimeout和withTimeoutOrNull操作超時(shí)

withTimeout的使用:

runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i ->
            Log.i("log","I'm sleeping $i ...")
            delay(500L)
        }
    }
}

輸出結(jié)果:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
隨后拋出了一個(gè)異常:
TimeoutCancellationException: Timed out waiting for 1300 ms

withTimeoutOrNull的使用:需要做一些各類使用超時(shí)的特別的額外操作,可以使用類似 withTimeout 的 withTimeoutOrNull函數(shù),并把這些會(huì)超時(shí)的代碼包裝在 try {...} catch (e: TimeoutCancellationException) {...} 代碼塊中,而 withTimeoutOrNull通過(guò)返回 null 來(lái)進(jìn)行超時(shí)操作,從而替代拋出一個(gè)異常:

runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            Log.i("log", "I'm sleeping $i ..")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    Log.i("log", "Result is $result")
}

輸出結(jié)果:
I'm sleeping 0 ..
I'm sleeping 1 ..
I'm sleeping 2 ..
Result is null

通道

send和receive的使用

runBlocking {
    val channel = Channel<Int>()//實(shí)例化通道
    launch {
        for (x in 1..5) channel.send(x * x)//使用send發(fā)送消息
    }
    repeat(5) {
        Log.i("log", "${channel.receive()}")//使用receive接收消息
    }
}

使用close關(guān)閉通道:一個(gè)通道可以通過(guò)被關(guān)閉來(lái)表明沒(méi)有更多的元素將會(huì)進(jìn)入通道。 在接收者中可以定期的使用 for 循環(huán)來(lái)從通道中接收元素。

runBlocking {
    val channel = Channel<Int>()//實(shí)例化通道
    launch {
        for (x in 1..5) channel.send(x * x)
        channel.close()//此處關(guān)閉通道
    }
    repeat(5) {
        //這里保證所有先前發(fā)送出去的元素都在通道關(guān)閉前被接收到。
        Log.i("log", "${channel.receive()}")
    }
}

構(gòu)建通道生產(chǎn)者
使用produce便捷的協(xié)程構(gòu)建器,可以很容易的在生產(chǎn)者端正確工作, 并且我們使用擴(kuò)展函數(shù) consumeEach 在消費(fèi)者端替代 for 循環(huán):

fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) send(x * x)
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }//使用consumeEach代替for循環(huán)
    println("Done!")
}

管道
管道是一種一個(gè)協(xié)程在流中開(kāi)始生產(chǎn)可能無(wú)窮多個(gè)元素的模式:

fun CoroutineScope.productNumbers() = produce<Int> {
    var x = 1
    while (true) {
        send(x++)
    }
}

并且另一個(gè)或多個(gè)協(xié)程開(kāi)始消費(fèi)這些流,做一些操作,并生產(chǎn)了一些額外的結(jié)果。 在下面的例子中,對(duì)這些數(shù)字僅僅做了平方操作:

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

啟動(dòng)并連接整個(gè)通道:

runBlocking {
     var nunbers = productNumbers()
     var square = square(nunbers)
     for (i in 1..5) Log.i("log", "${square.receive()}")
     coroutineContext.cancelChildren()//取消子協(xié)程
}

組合掛起函數(shù)
假如有兩個(gè)方法:

suspend fun doSomethingUsefulOne(): Int {
  delay(1000L) // 假設(shè)我們?cè)谶@里做了一些有用的事
  return 13
}

suspend fun doSomethingUsefulTwo(): Int {
  delay(1000L) // 假設(shè)我們?cè)谶@里也做了一些有用的事
  return 29
}

默認(rèn)順序調(diào)用

val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    Log.i("log", "The answer is ${one + two}")
}
Log.i("log", "Completed in $time ms")

明明是同步的寫法為什么不會(huì)阻塞主線程? 對(duì),因?yàn)閟uspend。
被suspend修飾的函數(shù)比普通函數(shù)多兩個(gè)操作(suspend 和 resume)
suspend:暫停當(dāng)前協(xié)程的執(zhí)行,保存所有的局部變量
resume:從協(xié)程被暫停的地方繼續(xù)執(zhí)行協(xié)程。
suspend修飾的函數(shù)并不意味著運(yùn)行在子線程中。

使用async并發(fā)

//使用async并發(fā)  這個(gè)比按順序調(diào)用的快了一倍。注意:使用協(xié)程進(jìn)行并發(fā)總是顯式的。
val time2 = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time2 ms")

惰性啟動(dòng)的async

//惰性啟動(dòng)的async:使用一個(gè)可選的參數(shù) start 并傳值 CoroutineStart.LAZY,可以對(duì) async 進(jìn)行惰性操作。 只有當(dāng)結(jié)果需
// 要被 await 或者如果一個(gè) start 函數(shù)被調(diào)用,協(xié)程才會(huì)被啟動(dòng)。
val time3 = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
    one.start()
    two.start()
    Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time3 ms")

使用coroutineScope實(shí)現(xiàn)async的結(jié)構(gòu)化并發(fā)

//使用coroutineScope實(shí)現(xiàn)async的結(jié)構(gòu)化并發(fā)
//這種情況下,如果在 concurrentSum 函數(shù)內(nèi)部發(fā)生了錯(cuò)誤,并且它拋出了一個(gè)異常, 所有在作用域中啟動(dòng)的協(xié)程都將會(huì)被取消。
suspend fun concurrentSum(): Int = coroutineScope {
   val one = async { doSomethingUsefulOne() }
   val two = async { doSomethingUsefulTwo() }
   one.await() + two.await()
}

協(xié)程上下文與調(diào)度器
協(xié)程上下文包括了一個(gè) 協(xié)程調(diào)度器 (CoroutineDispatcher),它確定了相應(yīng)的協(xié)程在執(zhí)行時(shí)使用一個(gè)或多個(gè)線程。協(xié)程調(diào)度器可以將協(xié)程的執(zhí)行局限在指定的線程中,調(diào)度它運(yùn)行在線程池中或讓它不受限的運(yùn)行。
所有的協(xié)程構(gòu)建器諸如 launch 和 async 接收一個(gè)可選的 CoroutineContext 參數(shù),它可以被用來(lái)顯式的為一個(gè)新協(xié)程或其它上下文元素指定一個(gè)調(diào)度器。

如果需要指定協(xié)程運(yùn)行的線程,就需要指定Dispatchers :
Dispatchers.Main:Android中的主線程,可以直接操作UI。
Dispatchers.IO:針對(duì)磁盤和網(wǎng)絡(luò)IO進(jìn)行了優(yōu)化,適合IO密集型的任務(wù),比如:讀寫文件,操作數(shù)據(jù)庫(kù)以及網(wǎng)絡(luò)請(qǐng)求。
Dispatchers.Default:適合CPU密集型的任務(wù),比如解析JSON文件,排序一個(gè)較大的list。
通過(guò)withContext()可以指定Dispatchers

啟動(dòng)一個(gè)協(xié)程需要CoroutineScope:
launch 啟動(dòng)一個(gè)協(xié)程,返回一個(gè)Job,可用來(lái)取消協(xié)程;有異常直接拋出。
async 啟動(dòng)一個(gè)帶返回結(jié)果的協(xié)程,可以通過(guò)Deferred.await()獲取結(jié)果;有異常并不會(huì)直接拋出,只會(huì)在調(diào)用 await 的時(shí)候拋出。
withContext 啟動(dòng)一個(gè)協(xié)程,傳入CoroutineContext改變協(xié)程運(yùn)行的上下文。

調(diào)度器的幾種形式:

runBlocking {
      launch {
         // 運(yùn)行在父協(xié)程的上下文中,即 runBlocking 主協(xié)程
         //I'm working in thread main
         Log.i("log", "main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(Dispatchers.Unconfined) {
         // 不受限的——將工作在主線程中
         //I'm working in thread DefaultDispatcher-worker-1
         Log.i("log", "Unconfined            : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(Dispatchers.Default) {
         // 將會(huì)獲取默認(rèn)調(diào)度器,一般使用這個(gè)默認(rèn)的就可以
         // I'm working in thread main
         Log.i("log", "Default               : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(newSingleThreadContext("MyOwnThread")) {
         // 將使它獲得一個(gè)新的線程
         //I'm working in thread MyOwnThread
         Log.i("log", "newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
      }
}

newSingleThreadContext 為協(xié)程的運(yùn)行啟動(dòng)了一個(gè)線程。 一個(gè)專用的線程是一種非常昂貴的資源。 在真實(shí)的應(yīng)用程序中兩者都必須被釋放,當(dāng)不再需要的時(shí)候,使用 close 函數(shù),或存儲(chǔ)在一個(gè)頂級(jí)變量中使它在整個(gè)應(yīng)用程序中被重用。

父協(xié)程和子協(xié)程
子協(xié)程:當(dāng)一個(gè)協(xié)程被其它協(xié)程在CoroutineScope中啟動(dòng)的時(shí)候, 它將通過(guò)CoroutineScope.coroutineContext來(lái)承襲上下文,并且這個(gè)新協(xié)程的Job將會(huì)成為父協(xié)程作業(yè)的子 作業(yè)。當(dāng)一個(gè)父協(xié)程被取消的時(shí)候,所有它的子協(xié)程也會(huì)被遞歸的取消。

如果在 foo 里協(xié)程啟動(dòng)了bar 協(xié)程,那么 bar 協(xié)程必須在 foo 協(xié)程之前完成。

然而,當(dāng) GlobalScope 被用來(lái)啟動(dòng)一個(gè)協(xié)程時(shí),它與作用域無(wú)關(guān)且是獨(dú)立被啟動(dòng)的。

runBlocking {
   // 啟動(dòng)一個(gè)協(xié)程來(lái)處理某種傳入請(qǐng)求(request)
   val request = launch {
   // 孵化了兩個(gè)子作業(yè), 其中一個(gè)通過(guò) GlobalScope 啟動(dòng)
       GlobalScope.launch {
           Log.i("log","job1: I run in GlobalScope and execute           independently!")
           delay(1000)
           Log.i("log","job1: I am not affected by cancellation of the request")
       }
       // 另一個(gè)則承襲了父協(xié)程的上下文
       launch {
           delay(100)
           Log.i("log","job2: I am a child of the request coroutine")
           delay(1000)
           Log.i("log","job2: I will not execute this line if my parent request is cancelled")
       }
    }
    delay(500)
    request.cancel() // 取消請(qǐng)求(request)的執(zhí)行
    delay(1000) // 延遲一秒鐘來(lái)看看發(fā)生了什么
    Log.i("log","main: Who has survived request cancellation?")
}

輸出結(jié)果:

job1: I run in GlobalScope 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?

父協(xié)程的職責(zé):一個(gè)父協(xié)程總是等待所有的子協(xié)程執(zhí)行結(jié)束。父協(xié)程并不顯式的跟蹤所有子協(xié)程的啟動(dòng)以及不必使用 Job.join 在最后的時(shí)候等待它們。

runBlocking {
    val request2 = launch {
        repeat(3) { i ->
            // 啟動(dòng)少量的子作業(yè)
            launch {
                delay((i + 1) * 200L) // 延遲 200 毫秒、400 毫秒、600 毫秒的時(shí)間
                Log.i("log2", "Coroutine $i is done")
            }
        }
        Log.i("log2", "request: I'm done and I don't explicitly join my children that are still active")
    }
    request2.join() // 等待請(qǐng)求的完成,包括其所有子協(xié)程,沒(méi)有join,下面的語(yǔ)句會(huì)提前輸出
    Log.i("log2", "Now processing of the request is complete")
}

輸出結(jié)果:

request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete

select的使用

select 表達(dá)式可以同時(shí)等待多個(gè)掛起函數(shù),并 選擇 第一個(gè)可用的。

使用send()和onReceive()方法來(lái)實(shí)現(xiàn)此功能。

fun CoroutineScope.fizz() = produce<String> {
    while (true) {
        delay(300)
        send("Fizz")
    }
}

fun CoroutineScope.buzz() = produce<String> {
    while (true) {
    delay(300)
        send("Buzz")
    }
}

runBlocking { 
    val fizz = fizz()
    val buzz = buzz()
    repeat(7) {
        selelctFizzAddBuzz(fizz, buzz)
    }
    coroutineContext.cancelChildren() // 取消 fizz 和 buzz 協(xié)程           
}

通道關(guān)閉時(shí)select:select 中的 onReceive 子句在已經(jīng)關(guān)閉的通道執(zhí)行會(huì)發(fā)生失敗,并導(dǎo)致相應(yīng)的 select 拋出異常。我們可以使用onReceiveOrNull子句在關(guān)閉通道時(shí)執(zhí)行特定操作。

suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
    select<String> {
        //<String>表示返回值為String
        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'"
        }
    }

runBlocking {
       val a = produce<String> {
            repeat(4) { send("Hello $it") }
       }
       val b = produce<String> {
            repeat(4) { send("World $it") }
       }
       repeat(8) { selectAorB(a, b) }
       coroutineContext.cancelChildren()
}

共享的可變狀態(tài)和并發(fā)

actor的使用

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
            val counter = counterActor() // create the actor
            withContext(Dispatchers.Default) {
                massiveRun {
                    counter.send(IncCounter)
                }
            }
            // send a message to get a counter value from an actor
            val response = CompletableDeferred<Int>()
            counter.send(GetCounter(response))
            Log.i("log","Counter = ${response.await()}")//Counter = 10000000
            counter.close() // shutdown the actor
        }
    }
}

sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply

// This function launches a new counter actor

fun CoroutineScope.counterActor() = actor<CounterMsg> {
    var counter = 0 // actor state
    for (msg in channel) { // iterate over incoming messages
        when (msg) {
            is IncCounter -> counter++
            is GetCounter -> msg.response.complete(counter)
        }
    }
}

suspend fun massiveRun(action: suspend () -> Unit) {
    val n = 1000
    val k = 10000
    val time = measureTimeMillis {
        coroutineScope {
            repeat(n) {
                launch {
                    repeat(k) { action() }
                }
            }
        }
    }
    Log.i("log","Completed ${n * k} actions in $time ms")// Completed 10000000 actions in 81301 ms
}

Activity和協(xié)程的生命周期以及結(jié)構(gòu)化并發(fā)

創(chuàng)建一個(gè)協(xié)程的基類,方法取消以及防止忘記

abstract class ScopedAppActivity: AppCompatActivity(),CoroutineScope by MainScope(){
    override fun onDestroy() {
        super.onDestroy()
        cancel()//在activity被銷毀的時(shí)候立即取消。
    }
}

1)CoroutineScope:不建議手動(dòng)實(shí)現(xiàn)此接口,建議使用委托實(shí)現(xiàn),按照慣例,范圍的上下文應(yīng)該包含作業(yè)的實(shí)例,以實(shí)現(xiàn)結(jié)構(gòu)化并發(fā)。
2)MainScope:自動(dòng)提供 Dispatchers.Main 以及父級(jí)任務(wù)。
3)每個(gè)coroutine構(gòu)建器(如launch,async等)都是CoroutineScope上的一個(gè)擴(kuò)展
4)每個(gè)coroutine構(gòu)建器(如launch、async等)和每個(gè)作用域函數(shù)(如coroutineScope、withContext等)都將自己的作用域和自己的作業(yè)實(shí)例提供給它運(yùn)行的內(nèi)部代碼塊。按照慣例,它們都要等待塊內(nèi)的所有協(xié)程完成,然后才能完成它們自己,從而強(qiáng)制執(zhí)行結(jié)構(gòu)化并發(fā)的原則。CoroutineScope應(yīng)該在具有定義良好的生命周期的實(shí)體上實(shí)現(xiàn)(或用作字段),這些實(shí)體負(fù)責(zé)啟動(dòng)子協(xié)同程序。

子類Activity實(shí)現(xiàn)基類

class MainActivity22 : ScopedAppActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        asyncShowData()
    }

    fun asyncShowData() = launch {
        //Activity的job作為父結(jié)構(gòu)時(shí),這里將在UI上下文中被調(diào)用
        showIOData()
    }

    suspend fun showIOData() {
        val deferred = async(Dispatchers.IO) {
            Log.i("aaaaaaaaaaaaaaaaaa", "111111")
            // 實(shí)現(xiàn)
            netRequest()
        }
        withContext(Dispatchers.Main) {
            //在UI中展示數(shù)據(jù)
            val data = deferred.await()
            Log.i("aaaaaaaaaaaaaaaaaa", "222222")
            Log.i("aaaaaaaaaaaaaaaaaa", "${data}")
        }
    }

    fun netRequest(): Int {
        return 666;
    }
}

使用協(xié)程操作UI更新

class MainActivity : AppCompatActivity() {
    val channel = Channel<Int>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //使用協(xié)程更新UI以及取消協(xié)程
        setup(hello, button)
        //在 UI 上下文中使用actors
        setup2(hello, button)
    }
}

fun View.onClick(action: suspend () -> Unit) {
    setOnClickListener {
        GlobalScope.launch(Dispatchers.Main) {
            action()
        }
    }
}

//更新UI只能在主線程中進(jìn)行
fun setup(hello: TextView, button: Button) {
    val job = GlobalScope.launch(Dispatchers.Main) {
        // 在主線程啟動(dòng)協(xié)程
        for (i in 100 downTo 1) { // 從 10 到 1 的倒計(jì)時(shí)
            hello.text = "Countdown $i ..." // 更新文本
            delay(500) // 等待半秒鐘
        }
        hello.text = "Done!"
    }
}

fun setup2(hello: TextView, button: Button) {
    button.onClick2 {
        for (i in 100 downTo 1) { // 從 10 到 1 的倒計(jì)時(shí)
            hello.text = "Countdown $i ..." // 更新文本
            delay(500) // 等待半秒鐘
        }
        hello.text = "Done!"
    }
}

fun View.onClick2(action: suspend (View) -> Unit) {
    // 啟動(dòng)一個(gè) actor
    val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
        for (event in channel) action(event)
    }
    // 設(shè)置一個(gè)監(jiān)聽(tīng)器來(lái)啟用 actor
    setOnClickListener {
        eventActor.offer(it)
    }
}

我們?cè)谥鱑I上下文中啟動(dòng)協(xié)程,我們可以在該協(xié)程內(nèi)部自如的更新UI,并同時(shí)調(diào)用就像delay這樣的 掛起函數(shù) 。當(dāng)delay函數(shù)的等待期間UI并不會(huì)凍結(jié),因?yàn)樗粫?huì)阻塞UI線程——它只會(huì)掛起協(xié)程。

Java中創(chuàng)建線程的兩種方式:Thread Runnable

協(xié)程它能替換掉Handler,AsyncTask 甚至是Rxjava來(lái)優(yōu)雅的解決異步問(wèn)題。

kotlin協(xié)程的三種啟動(dòng)方式:
1)runBlocking:T
runBlocking啟動(dòng)的協(xié)程任務(wù)會(huì)阻斷當(dāng)前線程,直到該協(xié)程執(zhí)行結(jié)束。
2)launch:Job
我們最常用的用于啟動(dòng)協(xié)程的方式,它最終返回一個(gè)Job類型的對(duì)象,這個(gè)Job類型的對(duì)象實(shí)際上是一個(gè)接口,它包涵了許多我們常用的方法。例如join()啟動(dòng)一個(gè)協(xié)程、cancel() 取消一個(gè)協(xié)程,該方式啟動(dòng)的協(xié)程任務(wù)是不會(huì)阻塞線程的
3)async/await:Deferred
async和await是兩個(gè)函數(shù),這兩個(gè)函數(shù)在我們使用過(guò)程中一般都是成對(duì)出現(xiàn)的。
async用于啟動(dòng)一個(gè)異步的協(xié)程任務(wù),await用于去得到協(xié)程任務(wù)結(jié)束時(shí)返回的結(jié)果,結(jié)果是通過(guò)一個(gè)Deferred對(duì)象返回的。

同步和異步
同步:一直等待結(jié)果。
異步:可能結(jié)果不會(huì)馬上返回,而是在函數(shù)執(zhí)行的某段時(shí)間后,我們通過(guò)輪詢查看執(zhí)行的結(jié)果,或通過(guò)函數(shù)的回調(diào)來(lái)告知我們結(jié)果。

協(xié)程內(nèi)存泄漏
1)如何避免泄漏呢?這其實(shí)就是CoroutineScope 的作用,通過(guò)launch或者async啟動(dòng)一個(gè)協(xié)程需要指定CoroutineScope,當(dāng)要取消協(xié)程的時(shí)候只需要調(diào)用CoroutineScope.cancel() ,kotlin會(huì)幫我們自動(dòng)取消在這個(gè)作用域里面啟動(dòng)的協(xié)程。

2)結(jié)構(gòu)化并發(fā)可以保證當(dāng)一個(gè)作用域被取消,作用域里面的所有協(xié)程會(huì)被取消。
結(jié)構(gòu)化并發(fā)保證當(dāng)suspend函數(shù)返回的時(shí)候,函數(shù)里面的所有工作都已經(jīng)完成。
結(jié)構(gòu)化并發(fā)保證當(dāng)協(xié)程出錯(cuò)時(shí),協(xié)程的調(diào)用者或者他的做作用域得到通知。
由此可見(jiàn),結(jié)構(gòu)化并發(fā)可以保證代碼更加安全,避免了協(xié)程的泄漏問(wèn)題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,263評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,543評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,874評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,095評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,358評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,612評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,330評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評(píng)論 2 370