協(xié)程通過(guò)替代回調(diào)(callback)來(lái)簡(jiǎn)化異步代碼。
協(xié)程的執(zhí)行其實(shí)是斷斷續(xù)續(xù)的: 執(zhí)行一段, 掛起來(lái), 再執(zhí)行一段, 再掛起來(lái), ...
每個(gè)掛起的地方是一個(gè)suspension point, 每一小段執(zhí)行是一個(gè)Continuation。
協(xié)程的執(zhí)行流被它的 "suspension point" 分割成了很多個(gè) "Continuation" 。
官方示例
runBlocking {
repeat(100000) {
//循環(huán)100000次
launch {
//開(kāi)啟一個(gè)協(xié)程
delay(1000L)
Log.i("log", "aaa")
}
}
}
delay 是一個(gè)特殊的 掛起函數(shù) ,它不會(huì)造成線程阻塞,但是會(huì)掛起協(xié)程,并且只能在協(xié)程中使用。當(dāng)這個(gè)協(xié)程處于等待狀態(tài)時(shí)該線程會(huì)返回線程池中,當(dāng)?shù)却Y(jié)束的時(shí)候,這個(gè)協(xié)程會(huì)在線程池中的空閑線程上恢復(fù)。
runBlocking并非coroutine-builder,所以它不需要CoroutineScope來(lái)調(diào)用,僅僅是能夠接收一個(gè)suspend lambda而已。
launch和cancle
val job = GlobalScope.launch(Dispatchers.Default) {
//推薦使用默認(rèn)的Dispatchers.Default上下文
//在一個(gè)協(xié)程環(huán)境中,執(zhí)行后臺(tái)耗時(shí)操作
repeat(100000) {
delay(1000L)// delay是一個(gè)特殊的掛起函數(shù) ,它不會(huì)造成線程阻塞,但是會(huì)掛起協(xié)程,并且只能在協(xié)程中使用。
Log.i("log", "aaa")
}
}
Log.i("log", "執(zhí)行主線程代碼")//這句代碼會(huì)立即執(zhí)行,因?yàn)閐elay不會(huì)阻塞主線程
tv.setOnClickListener {
job.cancel()//協(xié)程中,對(duì)這個(gè)任務(wù)進(jìn)行取消
}
async和await
GlobalScope.launch(Dispatchers.Default) {
val result = async {
netRequest()
}
Log.i("log", "${result.await()}")//在async方法結(jié)束的時(shí)候,就會(huì)調(diào)用await()方法
}
fun netRequest(): Int {
return 666
}
runBlocking執(zhí)行塊
fun play() = runBlocking {
GlobalScope.launch {
// 在后臺(tái)啟動(dòng)一個(gè)新的協(xié)程并繼續(xù)
delay(1000L)
Log.i("log", "World!")
}
Log.i("log", "Hello!") // 主協(xié)程在這里會(huì)立即執(zhí)行
delay(2000L) // 延遲 2 秒來(lái)保證 JVM 存活
}
輸出結(jié)果 Hello! World!
delay和runBlocking的區(qū)別:
delay:非阻塞的函數(shù)
runBlocking:會(huì)一直阻塞到塊中的代碼執(zhí)行完
join
fun play() = runBlocking {
val job = GlobalScope.launch {
// 在后臺(tái)啟動(dòng)一個(gè)新的協(xié)程并繼續(xù)
delay(1000L)
Log.i("log", "World!")
}
Log.i("log", "Hello!")
job.join()//等待直到子協(xié)程執(zhí)行結(jié)束 主協(xié)程與后臺(tái)作業(yè)的持續(xù)時(shí)間沒(méi)有任何關(guān)系了
Log.i("log", "子協(xié)程執(zhí)行結(jié)束")
}
輸出結(jié)果:Hello! World! 子協(xié)程執(zhí)行結(jié)束
結(jié)構(gòu)化的并發(fā):我們可以在執(zhí)行操作所在的指定作用域內(nèi)啟動(dòng)協(xié)程, 而不是像通常使用線程(線程總是全局的)那樣在 GlobalScope 中啟動(dòng)。
fun play() = runBlocking {
launch {
delay(1000L)
Log.i("log", "World!")
}
Log.i("log", "Hello,")
}
作用域構(gòu)建器
fun play() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
Log.i("log", "Task from runBlocking")
}
coroutineScope { // 創(chuàng)建一個(gè)協(xié)程作用域,等待所有子協(xié)程執(zhí)行完畢時(shí)不會(huì)阻塞當(dāng)前線程,并且在所有已啟動(dòng)子協(xié)程執(zhí)行完畢之前不會(huì)結(jié)束。r
launch {
delay(500L)
Log.i("log", "Task from nested launch")
}
delay(100L)
Log.i("log", "Task from coroutine scope") // 這一行會(huì)在內(nèi)嵌launch之前輸出
}
Log.i("log", "Coroutine scope is over")// 這一行在內(nèi)嵌launch執(zhí)行完畢后才輸出
}
輸出結(jié)果:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
掛起函數(shù):使用關(guān)鍵字suspend
fun play() = runBlocking{
launch {
downLoad()
}
Log.i("log", "Hello,")
}
suspend fun downLoad(){
Log.i("log", "World")
}
suspend方法只被允許在協(xié)程或另一個(gè)掛起函數(shù)中調(diào)用, 不能在協(xié)程外面調(diào)用。
suspend方法本質(zhì)上, 與普通方法有較大的區(qū)別, suspend方法的本質(zhì)是異步返回,在協(xié)程里面, 調(diào)用suspend方法, 異步的數(shù)據(jù)像同步一樣般return了。
調(diào)用suspend方法的詳細(xì)流程是:
在協(xié)程里, 如果調(diào)用了一個(gè)suspend方法, 協(xié)程就會(huì)掛起, 釋放自己的執(zhí)行權(quán), 但在協(xié)程掛起之前, suspend方法內(nèi)部一般會(huì)啟動(dòng)了另一個(gè)線程或協(xié)程, 我們暫且稱之為"分支執(zhí)行流"吧, 它的目的是運(yùn)算得到一個(gè)數(shù)據(jù)。
當(dāng)suspend方法里的“分支執(zhí)行流”完成后, 就會(huì)調(diào)用系統(tǒng)API重新恢復(fù)協(xié)程的執(zhí)行, 同時(shí)會(huì)數(shù)據(jù)返回給協(xié)程(如果有的話)。
在沒(méi)有協(xié)程的世界里, 通常異步的方法都需要接受一個(gè)callback用于發(fā)布運(yùn)算結(jié)果。在協(xié)程里, 所有接受callback的方法, 都可以轉(zhuǎn)成不需要callback的suspend方法。
suspend方法并不總是引起協(xié)程掛起, 只有其內(nèi)部的數(shù)據(jù)未準(zhǔn)備好時(shí)才會(huì)。
需要注意的是: await是suspend方法, 但async不是, 所以它才可以在協(xié)程外面調(diào)用, async只是啟動(dòng)了協(xié)程, async本身不會(huì)引起協(xié)程掛起, 傳給async的lambda(也就是協(xié)程體)才可能引起協(xié)程掛起。
取消
協(xié)程的取消是 協(xié)作 的。一段協(xié)程代碼必須協(xié)作才能被取消。 所有 kotlinx.coroutines
中的掛起函數(shù)都是 可被取消的 。它們檢查協(xié)程的取消, 并在取消時(shí)拋出 CancellationException。 然而,如果協(xié)程正在執(zhí)行計(jì)算任務(wù),并且沒(méi)有檢查取消的話,那么它是不能被取消的。
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 50) { // 一個(gè)執(zhí)行計(jì)算的循環(huán),只是為了占用 CPU
// 每秒打印消息兩次
if (System.currentTimeMillis() >= nextPrintTime) {
Log.i("log", "job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 等待一段時(shí)間
Log.i("log", "main: I'm tired of waiting!")
job.cancelAndJoin() // 取消一個(gè)作業(yè)并且等待它結(jié)束
Log.i("log", "main: Now I can quit.")
}
在調(diào)用取消后, 作業(yè)仍然運(yùn)行到了結(jié)束為止。
使計(jì)算代碼可取消
runBlocking {
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
//isActive:當(dāng)當(dāng)前作業(yè)仍處于活動(dòng)狀態(tài)(尚未完成且尚未取消)時(shí),返回true
while (isActive) { //使用isActive,可以被取消的計(jì)算循環(huán)
if (System.currentTimeMillis() >= nextPrintTime) {
Log.i("log", "job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)// delay a bit
Log.i("log", "main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
Log.i("log", "main: Now I can quit")
}
輸出結(jié)果:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
I'm tired of waiting!
Now I can quit
在finally中釋放資源
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: I'm running finally")
}
}
delay(1300L) // 延遲一段時(shí)間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業(yè)并且等待它結(jié)束
println("main: Now I can quit.")
運(yùn)行不能取消的代碼塊
任何嘗試在 finally
塊中調(diào)用掛起函數(shù)的行為都會(huì)拋出 CancellationException,因?yàn)檫@里持續(xù)運(yùn)行的代碼是可以被取消的。通常,這并不是一個(gè)問(wèn)題,所有良好的關(guān)閉操作(關(guān)閉一個(gè)文件、取消一個(gè)作業(yè)、或是關(guān)閉任何一種通信通道)通常都是非阻塞的,并且不會(huì)調(diào)用任何掛起函數(shù)。然而,在真實(shí)的案例中,當(dāng)你需要掛起一個(gè)被取消的協(xié)程,你可以將相應(yīng)的代碼包裝在 withContext(NonCancellable) {……}
中,并使用 withContext
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)//如果不用NonCancellable,這個(gè)delay不會(huì)執(zhí)行
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L) // 延遲一段時(shí)間
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消該作業(yè)并等待它結(jié)束
println("main: Now I can quit.")
使用 withTimeout和withTimeoutOrNull操作超時(shí)
withTimeout的使用:
runBlocking {
withTimeout(1300L) {
repeat(1000) { i ->
Log.i("log","I'm sleeping $i ...")
delay(500L)
}
}
}
輸出結(jié)果:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
隨后拋出了一個(gè)異常:
TimeoutCancellationException: Timed out waiting for 1300 ms
withTimeoutOrNull的使用:需要做一些各類使用超時(shí)的特別的額外操作,可以使用類似 withTimeout 的 withTimeoutOrNull函數(shù),并把這些會(huì)超時(shí)的代碼包裝在 try {...} catch (e: TimeoutCancellationException) {...}
代碼塊中,而 withTimeoutOrNull通過(guò)返回 null
來(lái)進(jìn)行超時(shí)操作,從而替代拋出一個(gè)異常:
runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
Log.i("log", "I'm sleeping $i ..")
delay(500L)
}
"Done" // will get cancelled before it produces this result
}
Log.i("log", "Result is $result")
}
輸出結(jié)果:
I'm sleeping 0 ..
I'm sleeping 1 ..
I'm sleeping 2 ..
Result is null
通道
send和receive的使用
runBlocking {
val channel = Channel<Int>()//實(shí)例化通道
launch {
for (x in 1..5) channel.send(x * x)//使用send發(fā)送消息
}
repeat(5) {
Log.i("log", "${channel.receive()}")//使用receive接收消息
}
}
使用close關(guān)閉通道:一個(gè)通道可以通過(guò)被關(guān)閉來(lái)表明沒(méi)有更多的元素將會(huì)進(jìn)入通道。 在接收者中可以定期的使用 for 循環(huán)來(lái)從通道中接收元素。
runBlocking {
val channel = Channel<Int>()//實(shí)例化通道
launch {
for (x in 1..5) channel.send(x * x)
channel.close()//此處關(guān)閉通道
}
repeat(5) {
//這里保證所有先前發(fā)送出去的元素都在通道關(guān)閉前被接收到。
Log.i("log", "${channel.receive()}")
}
}
構(gòu)建通道生產(chǎn)者
使用produce便捷的協(xié)程構(gòu)建器,可以很容易的在生產(chǎn)者端正確工作, 并且我們使用擴(kuò)展函數(shù) consumeEach 在消費(fèi)者端替代 for
循環(huán):
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
for (x in 1..5) send(x * x)
}
fun main() = runBlocking {
val squares = produceSquares()
squares.consumeEach { println(it) }//使用consumeEach代替for循環(huán)
println("Done!")
}
管道
管道是一種一個(gè)協(xié)程在流中開(kāi)始生產(chǎn)可能無(wú)窮多個(gè)元素的模式:
fun CoroutineScope.productNumbers() = produce<Int> {
var x = 1
while (true) {
send(x++)
}
}
并且另一個(gè)或多個(gè)協(xié)程開(kāi)始消費(fèi)這些流,做一些操作,并生產(chǎn)了一些額外的結(jié)果。 在下面的例子中,對(duì)這些數(shù)字僅僅做了平方操作:
fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
for (x in numbers) send(x * x)
}
啟動(dòng)并連接整個(gè)通道:
runBlocking {
var nunbers = productNumbers()
var square = square(nunbers)
for (i in 1..5) Log.i("log", "${square.receive()}")
coroutineContext.cancelChildren()//取消子協(xié)程
}
組合掛起函數(shù)
假如有兩個(gè)方法:
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假設(shè)我們?cè)谶@里做了一些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假設(shè)我們?cè)谶@里也做了一些有用的事
return 29
}
默認(rèn)順序調(diào)用
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
Log.i("log", "The answer is ${one + two}")
}
Log.i("log", "Completed in $time ms")
明明是同步的寫法為什么不會(huì)阻塞主線程? 對(duì),因?yàn)閟uspend。
被suspend修飾的函數(shù)比普通函數(shù)多兩個(gè)操作(suspend 和 resume)
suspend:暫停當(dāng)前協(xié)程的執(zhí)行,保存所有的局部變量
resume:從協(xié)程被暫停的地方繼續(xù)執(zhí)行協(xié)程。
suspend修飾的函數(shù)并不意味著運(yùn)行在子線程中。
使用async并發(fā)
//使用async并發(fā) 這個(gè)比按順序調(diào)用的快了一倍。注意:使用協(xié)程進(jìn)行并發(fā)總是顯式的。
val time2 = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time2 ms")
惰性啟動(dòng)的async
//惰性啟動(dòng)的async:使用一個(gè)可選的參數(shù) start 并傳值 CoroutineStart.LAZY,可以對(duì) async 進(jìn)行惰性操作。 只有當(dāng)結(jié)果需
// 要被 await 或者如果一個(gè) start 函數(shù)被調(diào)用,協(xié)程才會(huì)被啟動(dòng)。
val time3 = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
one.start()
two.start()
Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time3 ms")
使用coroutineScope實(shí)現(xiàn)async的結(jié)構(gòu)化并發(fā)
//使用coroutineScope實(shí)現(xiàn)async的結(jié)構(gòu)化并發(fā)
//這種情況下,如果在 concurrentSum 函數(shù)內(nèi)部發(fā)生了錯(cuò)誤,并且它拋出了一個(gè)異常, 所有在作用域中啟動(dòng)的協(xié)程都將會(huì)被取消。
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
協(xié)程上下文與調(diào)度器
協(xié)程上下文包括了一個(gè) 協(xié)程調(diào)度器 (CoroutineDispatcher),它確定了相應(yīng)的協(xié)程在執(zhí)行時(shí)使用一個(gè)或多個(gè)線程。協(xié)程調(diào)度器可以將協(xié)程的執(zhí)行局限在指定的線程中,調(diào)度它運(yùn)行在線程池中或讓它不受限的運(yùn)行。
所有的協(xié)程構(gòu)建器諸如 launch 和 async 接收一個(gè)可選的 CoroutineContext 參數(shù),它可以被用來(lái)顯式的為一個(gè)新協(xié)程或其它上下文元素指定一個(gè)調(diào)度器。
如果需要指定協(xié)程運(yùn)行的線程,就需要指定Dispatchers :
Dispatchers.Main:Android中的主線程,可以直接操作UI。
Dispatchers.IO:針對(duì)磁盤和網(wǎng)絡(luò)IO進(jìn)行了優(yōu)化,適合IO密集型的任務(wù),比如:讀寫文件,操作數(shù)據(jù)庫(kù)以及網(wǎng)絡(luò)請(qǐng)求。
Dispatchers.Default:適合CPU密集型的任務(wù),比如解析JSON文件,排序一個(gè)較大的list。
通過(guò)withContext()可以指定Dispatchers
啟動(dòng)一個(gè)協(xié)程需要CoroutineScope:
launch 啟動(dòng)一個(gè)協(xié)程,返回一個(gè)Job,可用來(lái)取消協(xié)程;有異常直接拋出。
async 啟動(dòng)一個(gè)帶返回結(jié)果的協(xié)程,可以通過(guò)Deferred.await()獲取結(jié)果;有異常并不會(huì)直接拋出,只會(huì)在調(diào)用 await 的時(shí)候拋出。
withContext 啟動(dòng)一個(gè)協(xié)程,傳入CoroutineContext改變協(xié)程運(yùn)行的上下文。
調(diào)度器的幾種形式:
runBlocking {
launch {
// 運(yùn)行在父協(xié)程的上下文中,即 runBlocking 主協(xié)程
//I'm working in thread main
Log.i("log", "main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
// 不受限的——將工作在主線程中
//I'm working in thread DefaultDispatcher-worker-1
Log.i("log", "Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
// 將會(huì)獲取默認(rèn)調(diào)度器,一般使用這個(gè)默認(rèn)的就可以
// I'm working in thread main
Log.i("log", "Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) {
// 將使它獲得一個(gè)新的線程
//I'm working in thread MyOwnThread
Log.i("log", "newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
}
newSingleThreadContext 為協(xié)程的運(yùn)行啟動(dòng)了一個(gè)線程。 一個(gè)專用的線程是一種非常昂貴的資源。 在真實(shí)的應(yīng)用程序中兩者都必須被釋放,當(dāng)不再需要的時(shí)候,使用 close 函數(shù),或存儲(chǔ)在一個(gè)頂級(jí)變量中使它在整個(gè)應(yīng)用程序中被重用。
父協(xié)程和子協(xié)程
子協(xié)程:當(dāng)一個(gè)協(xié)程被其它協(xié)程在CoroutineScope中啟動(dòng)的時(shí)候, 它將通過(guò)CoroutineScope.coroutineContext來(lái)承襲上下文,并且這個(gè)新協(xié)程的Job將會(huì)成為父協(xié)程作業(yè)的子 作業(yè)。當(dāng)一個(gè)父協(xié)程被取消的時(shí)候,所有它的子協(xié)程也會(huì)被遞歸的取消。
如果在 foo 里協(xié)程啟動(dòng)了bar 協(xié)程,那么 bar 協(xié)程必須在 foo 協(xié)程之前完成。
然而,當(dāng) GlobalScope 被用來(lái)啟動(dòng)一個(gè)協(xié)程時(shí),它與作用域無(wú)關(guān)且是獨(dú)立被啟動(dòng)的。
runBlocking {
// 啟動(dòng)一個(gè)協(xié)程來(lái)處理某種傳入請(qǐng)求(request)
val request = launch {
// 孵化了兩個(gè)子作業(yè), 其中一個(gè)通過(guò) GlobalScope 啟動(dòng)
GlobalScope.launch {
Log.i("log","job1: I run in GlobalScope and execute independently!")
delay(1000)
Log.i("log","job1: I am not affected by cancellation of the request")
}
// 另一個(gè)則承襲了父協(xié)程的上下文
launch {
delay(100)
Log.i("log","job2: I am a child of the request coroutine")
delay(1000)
Log.i("log","job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel() // 取消請(qǐng)求(request)的執(zhí)行
delay(1000) // 延遲一秒鐘來(lái)看看發(fā)生了什么
Log.i("log","main: Who has survived request cancellation?")
}
輸出結(jié)果:
job1: I run in GlobalScope and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?
父協(xié)程的職責(zé):一個(gè)父協(xié)程總是等待所有的子協(xié)程執(zhí)行結(jié)束。父協(xié)程并不顯式的跟蹤所有子協(xié)程的啟動(dòng)以及不必使用 Job.join 在最后的時(shí)候等待它們。
runBlocking {
val request2 = launch {
repeat(3) { i ->
// 啟動(dòng)少量的子作業(yè)
launch {
delay((i + 1) * 200L) // 延遲 200 毫秒、400 毫秒、600 毫秒的時(shí)間
Log.i("log2", "Coroutine $i is done")
}
}
Log.i("log2", "request: I'm done and I don't explicitly join my children that are still active")
}
request2.join() // 等待請(qǐng)求的完成,包括其所有子協(xié)程,沒(méi)有join,下面的語(yǔ)句會(huì)提前輸出
Log.i("log2", "Now processing of the request is complete")
}
輸出結(jié)果:
request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete
select的使用
select 表達(dá)式可以同時(shí)等待多個(gè)掛起函數(shù),并 選擇 第一個(gè)可用的。
使用send()和onReceive()方法來(lái)實(shí)現(xiàn)此功能。
fun CoroutineScope.fizz() = produce<String> {
while (true) {
delay(300)
send("Fizz")
}
}
fun CoroutineScope.buzz() = produce<String> {
while (true) {
delay(300)
send("Buzz")
}
}
runBlocking {
val fizz = fizz()
val buzz = buzz()
repeat(7) {
selelctFizzAddBuzz(fizz, buzz)
}
coroutineContext.cancelChildren() // 取消 fizz 和 buzz 協(xié)程
}
通道關(guān)閉時(shí)select:select 中的 onReceive 子句在已經(jīng)關(guān)閉的通道執(zhí)行會(huì)發(fā)生失敗,并導(dǎo)致相應(yīng)的 select 拋出異常。我們可以使用onReceiveOrNull子句在關(guān)閉通道時(shí)執(zhí)行特定操作。
suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
select<String> {
//<String>表示返回值為String
a.onReceiveOrNull { value ->
if (value == null)
"Channel 'a' is closed"
else
"a -> '$value'"
}
b.onReceiveOrNull { value ->
if (value == null)
"Channel 'b' is closed"
else
"b -> '$value'"
}
}
runBlocking {
val a = produce<String> {
repeat(4) { send("Hello $it") }
}
val b = produce<String> {
repeat(4) { send("World $it") }
}
repeat(8) { selectAorB(a, b) }
coroutineContext.cancelChildren()
}
共享的可變狀態(tài)和并發(fā)
actor的使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
val counter = counterActor() // create the actor
withContext(Dispatchers.Default) {
massiveRun {
counter.send(IncCounter)
}
}
// send a message to get a counter value from an actor
val response = CompletableDeferred<Int>()
counter.send(GetCounter(response))
Log.i("log","Counter = ${response.await()}")//Counter = 10000000
counter.close() // shutdown the actor
}
}
}
sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
// This function launches a new counter actor
fun CoroutineScope.counterActor() = actor<CounterMsg> {
var counter = 0 // actor state
for (msg in channel) { // iterate over incoming messages
when (msg) {
is IncCounter -> counter++
is GetCounter -> msg.response.complete(counter)
}
}
}
suspend fun massiveRun(action: suspend () -> Unit) {
val n = 1000
val k = 10000
val time = measureTimeMillis {
coroutineScope {
repeat(n) {
launch {
repeat(k) { action() }
}
}
}
}
Log.i("log","Completed ${n * k} actions in $time ms")// Completed 10000000 actions in 81301 ms
}
Activity和協(xié)程的生命周期以及結(jié)構(gòu)化并發(fā)
創(chuàng)建一個(gè)協(xié)程的基類,方法取消以及防止忘記
abstract class ScopedAppActivity: AppCompatActivity(),CoroutineScope by MainScope(){
override fun onDestroy() {
super.onDestroy()
cancel()//在activity被銷毀的時(shí)候立即取消。
}
}
1)CoroutineScope:不建議手動(dòng)實(shí)現(xiàn)此接口,建議使用委托實(shí)現(xiàn),按照慣例,范圍的上下文應(yīng)該包含作業(yè)的實(shí)例,以實(shí)現(xiàn)結(jié)構(gòu)化并發(fā)。
2)MainScope:自動(dòng)提供 Dispatchers.Main 以及父級(jí)任務(wù)。
3)每個(gè)coroutine構(gòu)建器(如launch,async等)都是CoroutineScope上的一個(gè)擴(kuò)展
4)每個(gè)coroutine構(gòu)建器(如launch、async等)和每個(gè)作用域函數(shù)(如coroutineScope、withContext等)都將自己的作用域和自己的作業(yè)實(shí)例提供給它運(yùn)行的內(nèi)部代碼塊。按照慣例,它們都要等待塊內(nèi)的所有協(xié)程完成,然后才能完成它們自己,從而強(qiáng)制執(zhí)行結(jié)構(gòu)化并發(fā)的原則。CoroutineScope應(yīng)該在具有定義良好的生命周期的實(shí)體上實(shí)現(xiàn)(或用作字段),這些實(shí)體負(fù)責(zé)啟動(dòng)子協(xié)同程序。
子類Activity實(shí)現(xiàn)基類
class MainActivity22 : ScopedAppActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
asyncShowData()
}
fun asyncShowData() = launch {
//Activity的job作為父結(jié)構(gòu)時(shí),這里將在UI上下文中被調(diào)用
showIOData()
}
suspend fun showIOData() {
val deferred = async(Dispatchers.IO) {
Log.i("aaaaaaaaaaaaaaaaaa", "111111")
// 實(shí)現(xiàn)
netRequest()
}
withContext(Dispatchers.Main) {
//在UI中展示數(shù)據(jù)
val data = deferred.await()
Log.i("aaaaaaaaaaaaaaaaaa", "222222")
Log.i("aaaaaaaaaaaaaaaaaa", "${data}")
}
}
fun netRequest(): Int {
return 666;
}
}
使用協(xié)程操作UI更新
class MainActivity : AppCompatActivity() {
val channel = Channel<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//使用協(xié)程更新UI以及取消協(xié)程
setup(hello, button)
//在 UI 上下文中使用actors
setup2(hello, button)
}
}
fun View.onClick(action: suspend () -> Unit) {
setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
action()
}
}
}
//更新UI只能在主線程中進(jìn)行
fun setup(hello: TextView, button: Button) {
val job = GlobalScope.launch(Dispatchers.Main) {
// 在主線程啟動(dòng)協(xié)程
for (i in 100 downTo 1) { // 從 10 到 1 的倒計(jì)時(shí)
hello.text = "Countdown $i ..." // 更新文本
delay(500) // 等待半秒鐘
}
hello.text = "Done!"
}
}
fun setup2(hello: TextView, button: Button) {
button.onClick2 {
for (i in 100 downTo 1) { // 從 10 到 1 的倒計(jì)時(shí)
hello.text = "Countdown $i ..." // 更新文本
delay(500) // 等待半秒鐘
}
hello.text = "Done!"
}
}
fun View.onClick2(action: suspend (View) -> Unit) {
// 啟動(dòng)一個(gè) actor
val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
for (event in channel) action(event)
}
// 設(shè)置一個(gè)監(jiān)聽(tīng)器來(lái)啟用 actor
setOnClickListener {
eventActor.offer(it)
}
}
我們?cè)谥鱑I上下文中啟動(dòng)協(xié)程,我們可以在該協(xié)程內(nèi)部自如的更新UI,并同時(shí)調(diào)用就像delay這樣的 掛起函數(shù) 。當(dāng)delay函數(shù)的等待期間UI并不會(huì)凍結(jié),因?yàn)樗粫?huì)阻塞UI線程——它只會(huì)掛起協(xié)程。
Java中創(chuàng)建線程的兩種方式:Thread Runnable
協(xié)程它能替換掉Handler,AsyncTask 甚至是Rxjava來(lái)優(yōu)雅的解決異步問(wèn)題。
kotlin協(xié)程的三種啟動(dòng)方式:
1)runBlocking:T
runBlocking啟動(dòng)的協(xié)程任務(wù)會(huì)阻斷當(dāng)前線程,直到該協(xié)程執(zhí)行結(jié)束。
2)launch:Job
我們最常用的用于啟動(dòng)協(xié)程的方式,它最終返回一個(gè)Job類型的對(duì)象,這個(gè)Job類型的對(duì)象實(shí)際上是一個(gè)接口,它包涵了許多我們常用的方法。例如join()啟動(dòng)一個(gè)協(xié)程、cancel() 取消一個(gè)協(xié)程,該方式啟動(dòng)的協(xié)程任務(wù)是不會(huì)阻塞線程的
3)async/await:Deferred
async和await是兩個(gè)函數(shù),這兩個(gè)函數(shù)在我們使用過(guò)程中一般都是成對(duì)出現(xiàn)的。
async用于啟動(dòng)一個(gè)異步的協(xié)程任務(wù),await用于去得到協(xié)程任務(wù)結(jié)束時(shí)返回的結(jié)果,結(jié)果是通過(guò)一個(gè)Deferred對(duì)象返回的。
同步和異步
同步:一直等待結(jié)果。
異步:可能結(jié)果不會(huì)馬上返回,而是在函數(shù)執(zhí)行的某段時(shí)間后,我們通過(guò)輪詢查看執(zhí)行的結(jié)果,或通過(guò)函數(shù)的回調(diào)來(lái)告知我們結(jié)果。
協(xié)程內(nèi)存泄漏
1)如何避免泄漏呢?這其實(shí)就是CoroutineScope 的作用,通過(guò)launch或者async啟動(dòng)一個(gè)協(xié)程需要指定CoroutineScope,當(dāng)要取消協(xié)程的時(shí)候只需要調(diào)用CoroutineScope.cancel() ,kotlin會(huì)幫我們自動(dòng)取消在這個(gè)作用域里面啟動(dòng)的協(xié)程。
2)結(jié)構(gòu)化并發(fā)可以保證當(dāng)一個(gè)作用域被取消,作用域里面的所有協(xié)程會(huì)被取消。
結(jié)構(gòu)化并發(fā)保證當(dāng)suspend函數(shù)返回的時(shí)候,函數(shù)里面的所有工作都已經(jīng)完成。
結(jié)構(gòu)化并發(fā)保證當(dāng)協(xié)程出錯(cuò)時(shí),協(xié)程的調(diào)用者或者他的做作用域得到通知。
由此可見(jiàn),結(jié)構(gòu)化并發(fā)可以保證代碼更加安全,避免了協(xié)程的泄漏問(wèn)題