圖片來自必應
本文是對官方文件中協程的教程的翻譯加上個人理解,也可以直接閱讀官方文件:Your first coroutine with Kotlin
協程可以認為是一個輕量級的執行緒,和執行緒一樣,它可以同時執行、等待執行或者馬上執行。它與執行緒最大的不同在於協程的開銷非常低,幾乎不需要開銷。我們可以建立數千個協程,並且只付出很少的效能損耗。從另一方面來說,真正的執行緒去開啟並且執行它是十分昂貴的,數千個執行緒對現代機器的效能來說是個十分嚴峻的挑戰。
- 引入協程
引入協程的方法很簡單,只需要在app的build.gradle 檔案中引入coroutine支援:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
}
複製程式碼
那麼我們如何開始使用協程? 讓我們來看看 lunch{}函式:
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可以做到等待結果返回,但是也沒有協程這樣方便簡潔
如果在主執行緒的話,必須要等待我們協程完成,否則上面的例子將會在“Hello”列印之前結束了。
讓我們將上面的Thread().sleep(2000)
這句程式碼註釋掉,那麼結果將會是先列印“Stop”,再列印“Hello”。
如果我們直接使用同樣的非阻塞方法 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協程中一些基本概念與使用,關於協程的更多用法會在之後的文章中再一一說明。