圖片來自必應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{}
, 在這方面它和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")
}
複製代碼
正如文章開頭提到的,協程最大的優勢就是能夠不經過阻塞而掛起線程,編譯器必需要經過一些特殊的代碼而去實現這個功能,因此咱們必需要顯式的說明那些可能會掛起(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協程中一些基本概念與使用,關於協程的更多用法會在以後的文章中再一一說明。