Kotlin-超詳細協程簡單易懂

協程通過替代回調(callback)來簡化異步代碼。

協程的執行其實是斷斷續續的: 執行一段, 掛起來, 再執行一段, 再掛起來, ...
每個掛起的地方是一個suspension point, 每一小段執行是一個Continuation。
協程的執行流被它的 "suspension point" 分割成了很多個 "Continuation" 。

官方示例

runBlocking {
            repeat(100000) {
                //循環100000次
                launch {
                    //開啟一個協程
                    delay(1000L)
                    Log.i("log", "aaa")
                }
            }
        }

delay 是一個特殊的 掛起函數 ,它不會造成線程阻塞,但是會掛起協程,并且只能在協程中使用。當這個協程處于等待狀態時該線程會返回線程池中,當等待結束的時候,這個協程會在線程池中的空閑線程上恢復。

runBlocking并非coroutine-builder,所以它不需要CoroutineScope來調用,僅僅是能夠接收一個suspend lambda而已。

launch和cancle

 val job = GlobalScope.launch(Dispatchers.Default) {
        //推薦使用默認的Dispatchers.Default上下文
        //在一個協程環境中,執行后臺耗時操作
      repeat(100000) {
            delay(1000L)// delay是一個特殊的掛起函數 ,它不會造成線程阻塞,但是會掛起協程,并且只能在協程中使用。
            Log.i("log", "aaa")
      }
}
Log.i("log", "執行主線程代碼")//這句代碼會立即執行,因為delay不會阻塞主線程
tv.setOnClickListener {
  job.cancel()//協程中,對這個任務進行取消
}

async和await

GlobalScope.launch(Dispatchers.Default) {
     val result = async {
           netRequest()
     }
     Log.i("log", "${result.await()}")//在async方法結束的時候,就會調用await()方法
}

fun netRequest(): Int {
    return 666
}

runBlocking執行塊

fun play() = runBlocking {
    GlobalScope.launch {
        // 在后臺啟動一個新的協程并繼續
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!") // 主協程在這里會立即執行
    delay(2000L)      // 延遲 2 秒來保證 JVM 存活
}

輸出結果 Hello! World!

delay和runBlocking的區別:
delay:非阻塞的函數
runBlocking:會一直阻塞到塊中的代碼執行完

join

fun play() = runBlocking {
    val job = GlobalScope.launch {
        // 在后臺啟動一個新的協程并繼續
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!")
    job.join()//等待直到子協程執行結束  主協程與后臺作業的持續時間沒有任何關系了
    Log.i("log", "子協程執行結束")
}

輸出結果:Hello! World! 子協程執行結束

結構化的并發:我們可以在執行操作所在的指定作用域內啟動協程, 而不是像通常使用線程(線程總是全局的)那樣在 GlobalScope 中啟動。

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

作用域構建器

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

    coroutineScope { // 創建一個協程作用域,等待所有子協程執行完畢時不會阻塞當前線程,并且在所有已啟動子協程執行完畢之前不會結束。r
        launch {
            delay(500L)
            Log.i("log", "Task from nested launch")
        }

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

輸出結果:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

掛起函數:使用關鍵字suspend

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

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

suspend方法只被允許在協程或另一個掛起函數中調用, 不能在協程外面調用。
suspend方法本質上, 與普通方法有較大的區別, suspend方法的本質是異步返回,在協程里面, 調用suspend方法, 異步的數據像同步一樣般return了。

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

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

runBlocking {
    val startTime = System.currentTimeMillis()

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

在調用取消后, 作業仍然運行到了結束為止。

使計算代碼可取消

runBlocking {
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        //isActive:當當前作業仍處于活動狀態(尚未完成且尚未取消)時,返回true
        while (isActive) { //使用isActive,可以被取消的計算循環
            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")
}

輸出結果:
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) // 延遲一段時間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業并且等待它結束
println("main: Now I can quit.")

運行不能取消的代碼塊

任何嘗試在 finally 塊中調用掛起函數的行為都會拋出 CancellationException,因為這里持續運行的代碼是可以被取消的。通常,這并不是一個問題,所有良好的關閉操作(關閉一個文件、取消一個作業、或是關閉任何一種通信通道)通常都是非阻塞的,并且不會調用任何掛起函數。然而,在真實的案例中,當你需要掛起一個被取消的協程,你可以將相應的代碼包裝在 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,這個delay不會執行
            println("job: And I've just delayed for 1 sec because I'm non-cancellable")
        }
    }
}
delay(1300L) // 延遲一段時間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業并等待它結束
println("main: Now I can quit.")

使用 withTimeout和withTimeoutOrNull操作超時

withTimeout的使用:

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

輸出結果:

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

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

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")
}

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

通道

send和receive的使用

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

使用close關閉通道:一個通道可以通過被關閉來表明沒有更多的元素將會進入通道。 在接收者中可以定期的使用 for 循環來從通道中接收元素。

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

構建通道生產者
使用produce便捷的協程構建器,可以很容易的在生產者端正確工作, 并且我們使用擴展函數 consumeEach 在消費者端替代 for 循環:

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循環
    println("Done!")
}

管道
管道是一種一個協程在流中開始生產可能無窮多個元素的模式:

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

并且另一個或多個協程開始消費這些流,做一些操作,并生產了一些額外的結果。 在下面的例子中,對這些數字僅僅做了平方操作:

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

啟動并連接整個通道:

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

組合掛起函數
假如有兩個方法:

suspend fun doSomethingUsefulOne(): Int {
  delay(1000L) // 假設我們在這里做了一些有用的事
  return 13
}

suspend fun doSomethingUsefulTwo(): Int {
  delay(1000L) // 假設我們在這里也做了一些有用的事
  return 29
}

默認順序調用

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

明明是同步的寫法為什么不會阻塞主線程? 對,因為suspend。
被suspend修飾的函數比普通函數多兩個操作(suspend 和 resume)
suspend:暫停當前協程的執行,保存所有的局部變量
resume:從協程被暫停的地方繼續執行協程。
suspend修飾的函數并不意味著運行在子線程中。

使用async并發

//使用async并發  這個比按順序調用的快了一倍。注意:使用協程進行并發總是顯式的。
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")

惰性啟動的async

//惰性啟動的async:使用一個可選的參數 start 并傳值 CoroutineStart.LAZY,可以對 async 進行惰性操作。 只有當結果需
// 要被 await 或者如果一個 start 函數被調用,協程才會被啟動。
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實現async的結構化并發

//使用coroutineScope實現async的結構化并發
//這種情況下,如果在 concurrentSum 函數內部發生了錯誤,并且它拋出了一個異常, 所有在作用域中啟動的協程都將會被取消。
suspend fun concurrentSum(): Int = coroutineScope {
   val one = async { doSomethingUsefulOne() }
   val two = async { doSomethingUsefulTwo() }
   one.await() + two.await()
}

協程上下文與調度器
協程上下文包括了一個 協程調度器 (CoroutineDispatcher),它確定了相應的協程在執行時使用一個或多個線程。協程調度器可以將協程的執行局限在指定的線程中,調度它運行在線程池中或讓它不受限的運行。
所有的協程構建器諸如 launch 和 async 接收一個可選的 CoroutineContext 參數,它可以被用來顯式的為一個新協程或其它上下文元素指定一個調度器。

如果需要指定協程運行的線程,就需要指定Dispatchers :
Dispatchers.Main:Android中的主線程,可以直接操作UI。
Dispatchers.IO:針對磁盤和網絡IO進行了優化,適合IO密集型的任務,比如:讀寫文件,操作數據庫以及網絡請求。
Dispatchers.Default:適合CPU密集型的任務,比如解析JSON文件,排序一個較大的list。
通過withContext()可以指定Dispatchers

啟動一個協程需要CoroutineScope:
launch 啟動一個協程,返回一個Job,可用來取消協程;有異常直接拋出。
async 啟動一個帶返回結果的協程,可以通過Deferred.await()獲取結果;有異常并不會直接拋出,只會在調用 await 的時候拋出。
withContext 啟動一個協程,傳入CoroutineContext改變協程運行的上下文。

調度器的幾種形式:

runBlocking {
      launch {
         // 運行在父協程的上下文中,即 runBlocking 主協程
         //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) {
         // 將會獲取默認調度器,一般使用這個默認的就可以
         // I'm working in thread main
         Log.i("log", "Default               : I'm working in thread ${Thread.currentThread().name}")
      }

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

newSingleThreadContext 為協程的運行啟動了一個線程。 一個專用的線程是一種非常昂貴的資源。 在真實的應用程序中兩者都必須被釋放,當不再需要的時候,使用 close 函數,或存儲在一個頂級變量中使它在整個應用程序中被重用。

父協程和子協程
子協程:當一個協程被其它協程在CoroutineScope中啟動的時候, 它將通過CoroutineScope.coroutineContext來承襲上下文,并且這個新協程的Job將會成為父協程作業的子 作業。當一個父協程被取消的時候,所有它的子協程也會被遞歸的取消。

如果在 foo 里協程啟動了bar 協程,那么 bar 協程必須在 foo 協程之前完成。

然而,當 GlobalScope 被用來啟動一個協程時,它與作用域無關且是獨立被啟動的。

runBlocking {
   // 啟動一個協程來處理某種傳入請求(request)
   val request = launch {
   // 孵化了兩個子作業, 其中一個通過 GlobalScope 啟動
       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")
       }
       // 另一個則承襲了父協程的上下文
       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() // 取消請求(request)的執行
    delay(1000) // 延遲一秒鐘來看看發生了什么
    Log.i("log","main: Who has survived request cancellation?")
}

輸出結果:

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?

父協程的職責:一個父協程總是等待所有的子協程執行結束。父協程并不顯式的跟蹤所有子協程的啟動以及不必使用 Job.join 在最后的時候等待它們。

runBlocking {
    val request2 = launch {
        repeat(3) { i ->
            // 啟動少量的子作業
            launch {
                delay((i + 1) * 200L) // 延遲 200 毫秒、400 毫秒、600 毫秒的時間
                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() // 等待請求的完成,包括其所有子協程,沒有join,下面的語句會提前輸出
    Log.i("log2", "Now processing of the request is complete")
}

輸出結果:

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 表達式可以同時等待多個掛起函數,并 選擇 第一個可用的。

使用send()和onReceive()方法來實現此功能。

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 協程           
}

通道關閉時select:select 中的 onReceive 子句在已經關閉的通道執行會發生失敗,并導致相應的 select 拋出異常。我們可以使用onReceiveOrNull子句在關閉通道時執行特定操作。

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()
}

共享的可變狀態和并發

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和協程的生命周期以及結構化并發

創建一個協程的基類,方法取消以及防止忘記

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

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

子類Activity實現基類

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

        asyncShowData()
    }

    fun asyncShowData() = launch {
        //Activity的job作為父結構時,這里將在UI上下文中被調用
        showIOData()
    }

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

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

使用協程操作UI更新

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

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

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

//更新UI只能在主線程中進行
fun setup(hello: TextView, button: Button) {
    val job = GlobalScope.launch(Dispatchers.Main) {
        // 在主線程啟動協程
        for (i in 100 downTo 1) { // 從 10 到 1 的倒計時
            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 的倒計時
            hello.text = "Countdown $i ..." // 更新文本
            delay(500) // 等待半秒鐘
        }
        hello.text = "Done!"
    }
}

fun View.onClick2(action: suspend (View) -> Unit) {
    // 啟動一個 actor
    val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
        for (event in channel) action(event)
    }
    // 設置一個監聽器來啟用 actor
    setOnClickListener {
        eventActor.offer(it)
    }
}

我們在主UI上下文中啟動協程,我們可以在該協程內部自如的更新UI,并同時調用就像delay這樣的 掛起函數 。當delay函數的等待期間UI并不會凍結,因為它不會阻塞UI線程——它只會掛起協程。

Java中創建線程的兩種方式:Thread Runnable

協程它能替換掉Handler,AsyncTask 甚至是Rxjava來優雅的解決異步問題。

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

同步和異步
同步:一直等待結果。
異步:可能結果不會馬上返回,而是在函數執行的某段時間后,我們通過輪詢查看執行的結果,或通過函數的回調來告知我們結果。

協程內存泄漏
1)如何避免泄漏呢?這其實就是CoroutineScope 的作用,通過launch或者async啟動一個協程需要指定CoroutineScope,當要取消協程的時候只需要調用CoroutineScope.cancel() ,kotlin會幫我們自動取消在這個作用域里面啟動的協程。

2)結構化并發可以保證當一個作用域被取消,作用域里面的所有協程會被取消。
結構化并發保證當suspend函數返回的時候,函數里面的所有工作都已經完成。
結構化并發保證當協程出錯時,協程的調用者或者他的做作用域得到通知。
由此可見,結構化并發可以保證代碼更加安全,避免了協程的泄漏問題

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。