本質上,協程像是輕量級的線程
callback hell 回調地獄
,也就是說可能出現大量嵌套代碼,這種代碼在視覺效果以及邏輯維護上都堪稱地獄級代碼,很容易給程序員帶來困擾。Rxjava
這種用於處理異步編程的框架,有各類操做符以及流式調用等特色方便進行異步編程,而協程在這方面和Rxjava
這種框架不一樣,協程作到了讓代碼看起來更直白更具備邏輯性,至於怎麼讓代碼看起來更通俗易懂,能夠看看接下來我在學習協程中我的的一些理解和總結舉兩個簡單的建立方法 runBlocking { }
以及 GlobalScope.launch { }
java
runBlocking { }
runBlocking
建立了一個協程,而且會阻塞當前線程,等待做用域也就是{}
內的代碼以及全部子協程結束,而且runBlocking
是有返回值的,可是因爲會阻塞線程,因此用的狀況很少,在這裏作一個入門講解程序員
GlobalScope.launch { }
經過這個方法建立出來的協程是一個頂層的協程,它的生命週期跟
application
是同樣的,沒有返回值,也不能夠取消,而且不會阻塞當前線程,這一點和runBlocking { }
正好相反編程
經過下面一段代碼讓你們瞭解一下用法bash
// 簡單的輸出一句Hello World
GlobalScope.launch {
print("Hello World")
}
複製代碼
launch
和Job
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
, 而後在須要啓動的時候調用Job
的join
方法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
複製代碼
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
並不會被執行到。