Kotlin協程學習之路【一】

協程介紹

本質上,協程像是輕量級的線程
在咱們編程的過程當中 不免會出現異步編程和一些回調函數,這就很容易出現callback hell 回調地獄 ,也就是說可能出現大量嵌套代碼,這種代碼在視覺效果以及邏輯維護上都堪稱地獄級代碼,很容易給程序員帶來困擾。
在這以前你們可能接觸比較多的是像Rxjava這種用於處理異步編程的框架,有各類操做符以及流式調用等特色方便進行異步編程,而協程在這方面和Rxjava這種框架不一樣,協程作到了讓代碼看起來更直白更具備邏輯性,至於怎麼讓代碼看起來更通俗易懂,能夠看看接下來我在學習協程中我的的一些理解和總結

建立協程

舉兩個簡單的建立方法 runBlocking { } 以及 GlobalScope.launch { }java

runBlocking { }

runBlocking建立了一個協程,而且會阻塞當前線程,等待做用域也就是{}內的代碼以及全部子協程結束,而且runBlocking 是有返回值的,可是因爲會阻塞線程,因此用的狀況很少,在這裏作一個入門講解程序員

GlobalScope.launch { }

經過這個方法建立出來的協程是一個頂層的協程,它的生命週期跟application是同樣的,沒有返回值,也不能夠取消,而且不會阻塞當前線程,這一點和runBlocking { } 正好相反編程

經過下面一段代碼讓你們瞭解一下用法bash

// 簡單的輸出一句Hello World
GlobalScope.launch {  
    print("Hello World")
}
複製代碼

launchJob

launch是以不阻塞的方式去啓動一個新的協程,須要在協程的範圍內去調用,好比上面提到runBlocking couroutineScope,而且會返回一個Job用來控制任務的開始取消等操做app

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
}
複製代碼

上面是launch的構造方法,有三個參數

  • context:能夠傳入一個調度器Dispatchers來控制協程啓動選項,默認的參數是CoroutineStart.DEFAULT也就是按當前的上下文環境去啓動,若是咱們想在IO線程中啓動,能夠傳入Dispatchers.IO 還有像Dispatchers.Main就是在主線程中去啓動
  • start:若是咱們想去控制協程的啓動時機,能夠經過start傳入一個CoroutineStart,默認的CoroutineStart是當即啓動,若是想要延時啓動能夠傳入CoroutineStart.LAZY, 而後在須要啓動的時候調用Jobjoin方法
  • block:讓協程在傳入的協程範圍內去運行

構建launch的方法講完以後就講一下Job的做用,Job有幾個經常使用的方法,像是join cancel cancelAndJoin

  • join:啓動協程任務而且會以一個非阻塞的方式去等待這個Job完成框架

  • cancel:取消任務異步

  • cancelAndJoin:綜合上面兩個方法,實際上內部就是先調用了cancel再調用join,也就是說會等待協程內的任務完成以後,再繼續往下執行代碼,若是是cancel的話,那麼調用的時候協程內的任務就會直接中斷異步編程

    以上兩種cancel,舉個在安卓中的實際場景,好比一個草稿箱功能 ,用戶在點擊退出以後彈出一個loading框,這個時候能夠用cancelAndJoin等待上傳完成以後再dissmiss,若是用戶選擇直接退出,那麼能夠用cancel函數

    另外被取消的協程,會拋出一個CancellationException異常,表示協程被正常取消,若是你沒有捕獲錯誤的話,不會打印到控制檯/日誌學習

  • isActive:這是一個判斷當前協程是否還存活的標記,能夠在任務中根據這個屬性決定任務是否繼續

在協程中若是你想要在任務結束以後作一些操做,那麼你能夠用try{}finally{}這樣的操做把任務邏輯寫在try中,在finally中釋放資源


做用域構建器

除了由不一樣的構建器提供協程做用域以外,還可使用 coroutineScope 構建器聲明本身的做用域。它會建立一個協程做用域而且在全部已啓動子協程執行完畢以前不會結束。coroutineScope 是非阻塞式的,是掛起函數,而runBlocking 是阻塞式,屬於常規的函數,可是二者都會等待各自做用域中的全部子協程結束

關於上面說的runBlocking { }是阻塞式,coroutineScope是非阻塞式,能夠經過下面一段代碼來體現
import kotlinx.coroutines.*

fun main() = runBlocking { 
    // 用launch啓動一個子協程
    launch { 
        delay(200L)
        println("Task from runBlocking")
    }
    
    // 建立一個協程做用域
    coroutineScope { 
        launch {
            delay(500L) 
            println("Task from nested launch")
        }
    
        delay(100L)
        println("Task from coroutine scope") // 這一行會在內嵌 launch 以前輸出
    }
    
    println("Coroutine scope is over") // 這一行在內嵌 launch 執行完畢後才輸出
}


最終的輸出結果:x
Task from coroutine scope 
Task from runBlocking 
Task from nested launch 
Coroutine scope is over

複製代碼
 這一段代碼取自kotlin官方文檔,用來講明runBlocking { }coroutineScope的阻塞以及非阻塞式區別,有的人可能會以爲有點疑惑,代碼上看起來coroutineScope後面的打印老是會在最後才輸出,看起來彷佛coroutineScope纔是阻塞式的
 然而並非這樣,Task from runBlocking這一段先打印相信你們能夠明白,接着代碼運行到了coroutineScope做用域裏,做用域中有子協程,相信你們也能明白做用域中打印的順序,那麼問題來了,爲何Coroutine scope is over老是在最後打印。
 實際上是由於上面所提到的阻塞和非阻塞式特色,coroutineScope做用域會以非阻塞式的等待全部子協程完成,因此內部第一個launch在運行到delay的時候並無阻塞住線程,而是繼續運行下去,因此會先打印延時比較短的Task from coroutine scope,以後delay時間到了協程切回去打印了Task from nested launch,而因爲coroutineScope是在runBlocking 當中,因此當couroutineScope沒有結束以前,runBlocking會阻塞當前線程等待,因此在coroutineScope內部delay沒有結束,沒有打印完成以前,最後一句println並不會被執行到。

這篇文章就暫時講這麼多知識點,接下來我也會根據本身在學習協程過程的一些疑惑以及總結陸續寫後續的相關文章

相關文章
相關標籤/搜索