官方描述html
協程經過將複雜性放入庫來簡化異步編程。程序的邏輯能夠在協程中順序地表達,而底層庫會爲咱們解決其異步性。該庫能夠將用戶代碼的相關部分包裝爲回調、訂閱相關事件、在不一樣線程(甚至不一樣機器)上調度執行,而代碼則保持如同順序執行同樣簡單。android
人話版編程
協程就像很是輕量級的線程。promise
協程是運行在單線程中的併發程序,避免了多線程併發機制中切換線程時帶來的線程上下文切換、線程狀態切換、線程初始化上的性能損耗,能大幅度唐提升併發性能bash
線程是由系統調度的,線程切換或線程阻塞的開銷都比較大。而協程依賴於線程,可是協程掛起時不須要阻塞線程,幾乎是無代價的,協程是由開發者控制的。因此協程也像用戶態的線程,很是輕量級,一個線程中能夠建立任意個協程。多線程
協程是跑在線程上的,一個線程能夠同時跑多個協程,每個協程則表明一個耗時任務,咱們手動控制多個協程之間的運行、切換,決定誰何時掛起,何時運行,何時喚醒,而不是線程那樣交給系統內核來操做去競爭CPU時間片,缺點是本質是個單線程,不能利用到單個CPU的多個核閉包
添加依賴kotlinx.coroutines併發
kotlinx.coroutines 是由 JetBrains 開發的功能豐富的協程庫。它包含本指南中涵蓋的不少啓用高級協程的原語,包括 launch、 async 等等。異步
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
}
複製代碼
固然你確定須要引入kotlin的async
buildscript {
ext.kotlin_version = '1.3.61'
}
repository {
jcenter()
}
複製代碼
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在後臺啓動一個新的協程並繼續
delay(1000L) // 非阻塞的等待 1 秒鐘(默認時間單位是毫秒)
println("World!") // 在延遲後打印輸出
}
println("Hello,") // 協程已在等待時主線程還在繼續
Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
}
複製代碼
delay 是一個特殊的 掛起函數 ,它不會形成線程阻塞,可是會 掛起 協程,而且只能在協程中使用。
調用了runBlocking 的線程會一直阻塞直到 runBlocking 內部的協程執行完畢。
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // 開始執行主協程
GlobalScope.launch { // 在後臺啓動一個新的協程並繼續
delay(1000L)
println("World!")
}
println("Hello,") // 主協程在這裏會當即執行
delay(2000L) // 延遲 2 秒來保證 JVM 存活
}
複製代碼
這裏的 runBlocking { …… } 做爲用來啓動頂層主協程的適配器。 咱們顯式指定了其返回類型 Unit,由於在 Kotlin 中 main 函數必須返回 Unit 類型。
async 建立帶返回值的協程,返回的是 Deferred 類
async 同 launch 區別就是 async 是有返回值的 async 返回的是 Deferred 類型,Deferred 繼承自 Job 接口,Job有的它都有,增長了一個方法 await ,這個方法接收的是 async 閉包中返回的值,async 的特色是不會阻塞當前線程,但會阻塞所在協程,也就是掛起
withContext 不建立新的協程,在指定協程上運行代碼塊
runBlocking 不是 GlobalScope 的 API,能夠獨立使用,區別是 runBlocking 裏面的 delay 會阻塞線程,而 launch 建立的不會
協程很輕量
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(100_000) { // 啓動大量的協程
launch {
delay(1000L)
print(".")
}
}
}
複製代碼
它啓動了 10 萬個協程,而且在一秒鐘後,每一個協程都輸出一個點。 如今,嘗試使用線程來實現。會發生什麼?(極可能你的代碼會產生某種內存不足的錯誤)
launch 建立協程, launch是個擴展函數,接受3個參數,前面2個是常規參數,最後一個是個對象式函數
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 的3個參數和返回值
CoroutineContext
能夠理解爲協程的上下文,在這裏咱們能夠設置 CoroutineDispatcher 協程運行的線程調度器,有 4種線程模式:
Dispatchers.Default
Dispatchers.IO 子線程
Dispatchers.Main 主線程
Dispatchers.Unconfined 沒指定,就是在當前線程
不寫的話就是 Dispatchers.Default 模式的,或者咱們能夠本身建立協程上下文,也就是線程池
//newSingleThreadContext 單線程,newFixedThreadPoolContext 線程池
val singleThreadContext = newSingleThreadContext("work")
GlobalScope.launch(singleThreadContext) {
}
複製代碼
CoroutineStart
啓動模式,默認是DEAFAULT,也就是建立就啓動;還有一個是LAZY,意思是等你須要它的時候,再調用啓動
val lazyStart = GlobalScope.launch(start = CoroutineStart.LAZY) {
RLogUtil.i("啓動了")
}
delay(1000)
lazyStart.start()
複製代碼
block
閉包方法體,定義協程內須要執行的操做
返回值
協程構建函數的返回值,能夠把返回值job當作協程對象自己,協程的操做方法都在job身上了
job.start() - 啓動協程,除了 lazy 模式,協程都不須要手動啓動
job.join() - 等待協程執行完畢
job.cancel() - 取消一個協程
job.cancelAndJoin() - 等待協程執行完畢而後再取消
suspend 修飾符
當你對這段代碼執行「提取函數」重構時,你會獲得一個帶有 suspend 修飾符的新函數。 那是你的第一個掛起函數
在協程中表現爲標記、切換方法、代碼段,協程裏使用suspend關鍵字修飾方法,既該方法能夠被協程掛起,沒用suspend修飾的方法不能參與協程任務,suspend修飾的方法只能在協程中只能與另外一個suspend修飾的方法使用
// 這是你的第一個掛起函數
suspend fun doWorld() {
delay(1000L)
println("World!")
}
複製代碼
使用job.cancel()取消,也可使用job.cancelAndJoin() - 等待協程執行完畢而後再取消
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延遲一段時間
println("main: I'm tired of waiting!")
job.cancel() // 取消該做業
job.join() // 等待做業執行結束
println("main: Now I can quit.")
}
複製代碼
取消是協做的這裏須要注意的是一段協程代碼必須協做才能被取消。 全部 kotlinx.coroutines 中的掛起函數都是 可被取消的 。它們檢查協程的取消, 並在取消時拋出CancellationException。然而,若是協程正在執行計算任務,而且沒有檢查取消的話,那麼它是不能被取消的
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // 一個執行計算的循環,只是爲了佔用 CPU
// 每秒打印消息兩次
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 等待一段時間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消一個做業而且等待它結束
println("main: Now I can quit.")
}
複製代碼
job: I'm sleeping 0 ... job: I'm sleeping 1 ...
job: I'm sleeping 2 ... main: I'm tired of waiting!
job: I'm sleeping 3 ... job: I'm sleeping 4 ...
main: Now I can quit.
複製代碼
咱們有兩種方法來使執行計算的代碼能夠被取消。第一種方法是按期調用掛起函數來檢查取消。對於這種目的 yield 是一個好的選擇。 另外一種方法是顯式的檢查取消狀態
將前一個示例中的 while (i < 5) 替換爲 while (isActive) 並從新運行它。結果顯示是能夠取消的
咱們一般使用以下的方法處理在被取消時拋出 CancellationException 的可被取消的掛起函數。好比說,try {……} finally {……} 表達式以及 Kotlin 的 use 函數通常在協程被取消的時候執行它們的終結動做
在實踐中絕大多數取消一個協程的理由是它有可能超時。 當你手動追蹤一個相關 Job 的引用並啓動了一個單獨的協程在延遲後取消追蹤,這裏已經準備好使用 withTimeout 函數來作這件事。
withTimeout 拋出了 TimeoutCancellationException,它是 CancellationException 的子類 來看看示例代碼:
import kotlinx.coroutines.*
fun main() = runBlocking {
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
}
複製代碼
在概念上,async 就相似於 launch。它啓動了一個單獨的協程,這是一個輕量級的線程並與其它全部的協程一塊兒併發的工做。不一樣之處在於 launch 返回一個 Job 而且不附帶任何結果值,而 async 返回一個 Deferred —— 一個輕量級的非阻塞 future, 這表明了一個將會在稍後提供結果的 promise。你可使用 .await() 在一個延期的值上獲得它的最終結果, 可是 Deferred 也是一個 Job,因此若是須要的話,你能夠取消它。
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假設咱們在這裏作了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假設咱們在這裏也作了些有用的事
return 29
}
複製代碼
她們之間是會同時執行doSomethingUsefulOne()和doSomethingUsefulTwo()不使用的話則是按順序執行
惰性啓動的 async
可選的,async 能夠經過將 start 參數設置爲 CoroutineStart.LAZY 而變爲惰性的。 在這個模式下,只有結果經過 await 獲取的時候協程纔會啓動,或者在 Job 的 start 函數調用的時候。
上述內容也只是帶大家熟悉一下協程,學習協程可能會費些時間,協程和線程相似可是和線程有很大區別,特別須要注意一下協程內的使用順序,協程是很輕量級的,很是適合作一些併發任務,協程仍是有一些高級操做的,如異步流點(看着有點像RxJava)熟練了協程以後基本就能夠替代RxJava,協程三連走起!
你們看到這裏也基本熟悉了kotlin協程的使用了,本文大多數內容源自於kotlin官網,關於協程仍是一些高級用法,我們下期再見!
感謝