Kotlin coroutine之協程基礎

圖片來自必應html

本文是對官方文檔中協程的教程的翻譯加上我的理解,也能夠直接閱讀官方文檔:Your first coroutine with Kotlinandroid

協程能夠認爲是一個輕量級的線程,和線程同樣,它能夠同時運行、等待運行或者立刻運行。它與線程最大的不一樣在於協程的開銷很是低,幾乎不須要開銷。咱們能夠建立數千個協程,而且只付出不多的性能損耗。從另外一方面來講,真正的線程去開啓而且運行它是十分昂貴的,數千個線程對現代機器的性能來講是個十分嚴峻的挑戰。app

  • 引入協程

引入協程的方法很簡單,只須要在app的build.gradle 文件中引入coroutine支持:jvm

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
}
複製代碼

那麼咱們如何開始使用協程? 讓咱們來看看 lunch{}函數:async

GlobalScope.lunch{
    ...
}
複製代碼

上述代碼將會開啓一個新的協程, GlobalScope 表示該協程的生命週期僅受整個應用的生命週期影響。固然咱們也能夠建立一個新的協程基於某一個線程的生命週期,例如:函數

CoroutineScope(newSingleThreadContext("thread-1")).launch {  }
複製代碼

在默認狀況下,協程會運行在線程共享池。在基於協程的程序中線程仍然會存在,可是一個線程可以運行不少協程,因此咱們並不須要建立不少的線程, 咱們來看使用協程的一整段代碼:性能

fun main(args: Array<String>){
    println("Start")
    
    GlobalScope.launch{
        delay(1000)
        println("Hello")
    }
    
    Thread.sleep(2000)
    println("Stop")
}
複製代碼

從上面的代碼能夠看出來,咱們可使用delay() 函數相似Thread() 類中的 sleep() 方法,可是這個方法的好處在於:它並不會像Thread().sleep() 那樣會阻塞線程,而僅僅是暫停當前的協程。當協程暫停的時候,當前的線程將會釋放,當協程暫停結束的時候,它將會在線程池中的空閒的線程上恢復,這樣就意味着,若是咱們使用協程,咱們就能夠不用像線程那樣去使用回調處理返回結果,雖然RxJava能夠作到等待結果返回,可是也沒有協程這樣方便簡潔gradle

若是在主線程的話,必需要等待咱們協程完成,不然上面的例子將會在「Hello」打印以前結束了。ui

讓咱們將上面的Thread().sleep(2000) 這句代碼註釋掉,那麼結果將會是先打印「Stop」,再打印「Hello」。spa

若是咱們直接使用一樣的非阻塞方法 delay() 在主線程內,將會出現編譯錯誤:

Suspend functions are only allowed to be called from a coroutine or another suspend function

這個錯誤是由於咱們使用了delay( ) 而沒有在任何的協程中,咱們能夠經過runBlocking{} 來啓動一個協程而且阻塞直到其完成:

runBlocking{
    delay(1000)
}
複製代碼

如今,對於上面的例子來講,首先會打印「Start」 ,而後會運行到launch{},而後會運行runBlocking{} 直到它完成,而後打印「Stop」,與此同時第一個協程完成而且打印「Hello」。

  • async: 返回協程的值

還有一種開啓一個協程的方法爲async{}, 在這方面它和launch{} 具備同樣的效果,可是async{}會返回一個Deferred<T> 的實例,這個實例有一個方法await(),這個方法能夠返回協程的結果。

讓咱們再來看一段代碼,先來運行一百萬個協程,而且將它們返回的Deferred對象保存起來。而後計算結果:

val deferred = (1..1_000_000).map{
    n -> async{
        n
    }
}
複製代碼

當全部的都啓動了以後,咱們顯然須要收集它們的結果:

val sum = deferred.sumBy{it.await()}
複製代碼

上面的代碼看上去好像沒有什麼問題,咱們把每一個協程的結果拿到以後對其求和,看上去好像一切正常,可是編譯器卻報錯了:

Suspend functions are only allowed to be called from a coroutine or another suspend function
複製代碼

顯然,await() 不可以被使用在協程以外,由於await() 會暫停協程知道它完成,然而只有協程可以被不阻塞的暫停,因此,咱們應該將await() 寫在協程裏面:

runBlocking{
    val sum = deferred.sumBy{it.await()}
    println("Sum: $sum")
}
複製代碼
  • 掛起函數(Suspending functions)

正如文章開頭提到的,協程最大的優勢就是能夠不經過阻塞而掛起線程,編譯器必需要經過一些特殊的代碼而去實現這個功能,因此咱們必需要顯式的說明那些可能會掛起(suspend)的代碼,因此可使用suspend去說明:

suspend fun workload(n: Int): Int{
    delay(1000)
    return n
}
複製代碼

當咱們使用suspend顯式的說明workload() 函數可能會suspend以後,當咱們從協程中調用它,編譯器就會知道這個函數將會suspend而且作好相應的準備:

async{
    workload(n)
}
複製代碼

這時workload(n) 將可以從協程或者其餘的suspend函數中調用,可是不可以在協程之外調用。相應的,delay()await() 是被默認聲明爲suspend的, 這就是爲何必需要在runBlocking{}launch{}async{} 中才可以調用它們的緣由。

以上就是kotlin協程中一些基本概念與使用,關於協程的更多用法會在以後的文章中再一一說明。

相關文章
相關標籤/搜索