協程雖然是微執行緒,但是並不會和某一個特定的執行緒繫結,它可以在A執行緒中執行,並經過某一個時刻的掛起(suspend),等下次排程到恢復執行的時候,很可能會在B執行緒中執行。
一. withContext
與 launch、async、runBlocking 類似 withContext 也屬於 Coroutine builders。不過與他們不同的是,其他幾個都是建立一個新的協程,而 withContext 不會建立新的協程。withContext 允許更改協程的執行執行緒,withContext 在使用時需要傳遞一個 CoroutineContext 。
launch {
val result1 = withContext(CommonPool) {
delay(2000)
1
}
val result2 = withContext(CommonPool) {
delay(1000)
2
}
val result = result1 + result2
println(result)
}
Thread.sleep(5000)
複製程式碼
執行結果:
3
複製程式碼
withContext 可以有返回值,這一點類似 async。async 建立的協程通過 await() 方法將值返回。而 withContext 可以直接返回。
launch {
val result1 = async {
delay(2000)
1
}
val result2 = async {
delay(1000)
2
}
val result = result1.await() + result2.await()
println(result)
}
Thread.sleep(5000)
複製程式碼
執行結果:
3
複製程式碼
二. 共享執行緒池
在上述的例子中,withContext 使用了 CommonPool。CommonPool 繼承了 CoroutineDispatcher,表示使用執行緒池來執行協程的任務。
CommonPool 有點類似於 RxJava 的 Schedulers.computation(),主要是用於CPU密集型的計算任務。
CommonPool 使用 pool 來執行 block。
override fun dispatch(context: CoroutineContext, block: Runnable) =
try { (pool ?: getOrCreatePoolSync()).execute(timeSource.trackTask(block)) }
catch (e: RejectedExecutionException) {
timeSource.unTrackTask()
DefaultExecutor.execute(block)
}
複製程式碼
如果 pool 為空,則呼叫 getOrCreatePoolSync() 方法來建立 pool。
@Synchronized
private fun getOrCreatePoolSync(): Executor =
pool ?: createPool().also { pool = it }
複製程式碼
此時,createPool() 方法是正在建立 pool 的方法。
首先,安全管理器不為空的話,使用 createPlainPool() 來建立 pool。 否則,嘗試建立一個 ForkJoinPool,不行的話還是使用 createPlainPool() 來建立 pool。
private fun createPool(): ExecutorService {
if (System.getSecurityManager() != null) return createPlainPool()
val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
?: return createPlainPool()
if (!usePrivatePool) {
Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
?.let { return it }
}
Try { fjpClass.getConstructor(Int::class.java).newInstance(parallelism) as? ExecutorService }
?. let { return it }
return createPlainPool()
}
複製程式碼
createPlainPool() 會使用 Executors.newFixedThreadPool() 來建立執行緒池。
private fun createPlainPool(): ExecutorService {
val threadId = AtomicInteger()
return Executors.newFixedThreadPool(parallelism) {
Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
}
}
複製程式碼
CommonPool 的建立原理大致瞭解之後,通過原始碼發現 CoroutineContext 預設的 CoroutineDispatcher 就是 CommonPool。
/**
* This is the default [CoroutineDispatcher] that is used by all standard builders like
* [launch], [async], etc if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
*
* It is currently equal to [CommonPool], but the value is subject to change in the future.
*/
@Suppress("PropertyName")
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool
複製程式碼
常見的 CoroutineDispatcher 還可以通過 ThreadPoolDispatcher 的 newSingleThreadContext()、newFixedThreadPoolContext()來建立,以及Executor 的擴充套件函式 asCoroutineDispatcher() 來建立。
在 Android 中,還可以使用UI。它顧名思義,在 Android 主執行緒上排程執行。
三. 可取消的協程
Job、Deferred 物件都可以取消任務。
3.1 cancel()
使用 cancel() 方法:
val job = launch {
delay(1000)
println("Hello World!")
}
job.cancel()
println(job.isCancelled)
Thread.sleep(2000)
複製程式碼
執行結果:
true
複製程式碼
true表示job已經被取消了,並沒有列印"Hello World!"
3.2 cancelAndJoin()
使用 cancelAndJoin() 方法:
runBlocking<Unit> {
val job = launch {
repeat(100) { i ->
println("count time: $i")
delay(500)
}
}
delay(2100)
job.cancelAndJoin()
}
複製程式碼
執行結果:
count time: 0
count time: 1
count time: 2
count time: 3
count time: 4
複製程式碼
cancelAndJoin() 等價於使用了 cancel() 和 join()。
join() 方法用於等待已啟動協程的完成,並且它不會傳播其異常。 但是,崩潰的子協程也會取消其父協程,並帶有相應的異常。
3.3 檢查協程的取消標記
如果一個協程一直在執行計算,沒有去檢查取消標記,它就無法取消。即使呼叫了cancel() 或者 cancelAndJoin()。
runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
var tempTime = startTime
var i = 0
while (i < 100) {
if (System.currentTimeMillis() >= tempTime) {
println("count time: ${i++}")
tempTime += 500L
}
}
}
delay(2100)
job.cancelAndJoin()
}
複製程式碼
上述程式碼仍然會列印100次。
如果使用isActive
檢查取消標記,則Job 或 Deferred 的任務可以被取消:
runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
var tempTime = startTime
var i = 0
while (isActive) {
if (System.currentTimeMillis() >= tempTime) {
println("count time: ${i++}")
tempTime += 500L
}
}
}
delay(2100)
job.cancelAndJoin()
}
複製程式碼
執行結果:
count time: 0
count time: 1
count time: 2
count time: 3
count time: 4
複製程式碼
isActive 是 CoroutineScope 的屬性:
package kotlinx.coroutines.experimental
import kotlin.coroutines.experimental.*
import kotlin.internal.*
/**
* Receiver interface for generic coroutine builders, so that the code inside coroutine has a convenient
* and fast access to its own cancellation status via [isActive].
*/
public interface CoroutineScope {
/**
* Returns `true` when this coroutine is still active (has not completed and was not cancelled yet).
*
* Check this property in long-running computation loops to support cancellation:
* ```
* while (isActive) {
* // do some computation
* }
* ```
*
* This property is a shortcut for `coroutineContext.isActive` in the scope when
* [CoroutineScope] is available.
* See [coroutineContext][kotlin.coroutines.experimental.coroutineContext],
* [isActive][kotlinx.coroutines.experimental.isActive] and [Job.isActive].
*/
public val isActive: Boolean
/**
* Returns the context of this coroutine.
*
* @suppress: **Deprecated**: Replaced with top-level [kotlin.coroutines.experimental.coroutineContext].
*/
@Deprecated("Replace with top-level coroutineContext",
replaceWith = ReplaceWith("coroutineContext",
imports = ["kotlin.coroutines.experimental.coroutineContext"]))
@LowPriorityInOverloadResolution
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
public val coroutineContext: CoroutineContext
}
複製程式碼
總結:
本文介紹了三個部分:withContext的使用,CommonPool的建立以及如何取消協程。其中,還捎帶介紹了 async 和 await 的使用。
該系列的相關文章:
Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公眾號二維碼並關注,期待與您的共同成長和進步。