協程介紹
本質上,協程像是輕量級的執行緒
在我們程式設計的過程中 難免會出現非同步程式設計和一些回撥函式,這就很容易出現callback hell 回撥地獄
,也就是說可能出現大量巢狀程式碼,這種程式碼在視覺效果以及邏輯維護上都堪稱地獄級程式碼,很容易給程式設計師帶來困擾。
在這之前大家可能接觸比較多的是像Rxjava
這種用於處理非同步程式設計的框架,有各種操作符以及流式呼叫等特點方便進行非同步程式設計,而協程在這方面和Rxjava
這種框架不同,協程做到了讓程式碼看起來更直白更具有邏輯性,至於怎麼讓程式碼看起來更通俗易懂,可以看看接下來我在學習協程中個人的一些理解和總結
建立協程
舉兩個簡單的建立方法 runBlocking { }
以及 GlobalScope.launch { }
runBlocking { }
runBlocking
建立了一個協程,並且會阻塞當前執行緒,等待作用域也就是{}
內的程式碼以及所有子協程結束,並且runBlocking
是有返回值的,但是由於會阻塞執行緒,所以用的情況不多,在這裡做一個入門講解
GlobalScope.launch { }
通過這個方法建立出來的協程是一個頂層的協程,它的生命週期跟
application
是一樣的,沒有返回值,也不可以取消,並且不會阻塞當前執行緒,這一點和runBlocking { }
正好相反
通過下面一段程式碼讓大家瞭解一下用法
// 簡單的輸出一句Hello World
GlobalScope.launch {
print("Hello World")
}
複製程式碼
launch
和Job
launch
是以不阻塞的方式去啟動一個新的協程,需要在協程的範圍內去呼叫,比如上面提到runBlocking
couroutineScope
,並且會返回一個Job
用來控制任務的開始取消等操作
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
複製程式碼