感觉协程看了很久,仍然是云山雾绕的,一脸懵,直到看了 [码上开学]协程系列视频(扔物线,共3集)扔物线的讲解才感觉有一些明白,以前把协程想的太复杂了,其实它就是一个线程框架,所以还是要先了解概念,再学习怎么用,不然真的很痛苦。
接下来咱们先学习一下Kotlin协程中的概念,CoroutineScope, CoroutineContext, suspend, Job, Deferred,launch, async, withContext, coroutineScope,他们是什么,用来做什么。
然后学习Kotlin协程的使用,应该还有一篇文章学习Kotlin协程的原理,这里先按下不表。
协程就是一个线程框架,suspend就是一个提醒,用来告诉调用者这是一个耗时任务,需要在其他suspend方法或协程中调用。
CoroutineScope
CoroutineScope是协程的作用域,用于管理协程,管理的内容包括协程的启动方式和协程的生命周期。
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
CoroutineScope被定义为一个接口,拥有类型为CoroutineContext的属性。
创建CoroutineScope推荐使用CoroutineScope()和MainScope()工厂方法。
MainScope()用来创建在主线程执行的CoroutineScope。
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
class MyAndroidActivity {
private val scope = MainScope() + CoroutineName("MyAndroidActivity")
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
}
GlobalScope是CoroutineScope的子类,它是一个单例类,我们并不推荐使用它。
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
runBlocking()是一个顶层函数,它可以在CoroutineScope中运行协程,但它会阻塞当前线程,直到其内部所有逻辑及子协程逻辑全部执行完成,从源码中可知,它是通过死循环的方式等待逻辑执行完成的,不推荐在开发中使用。
CoroutineContext
CoroutineContext 是协程上下文,表示协程的运行环境,它是一个带索引的集合,集合的元素是Element。
CoroutineContext是一个接口,声明了几个常用的方法。
public interface Element : CoroutineContext {
/**
* A key of this coroutine context element.
*/
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
Element也是一个接口,它是CoroutineContext的子接口。
咱们平时用到的Job,Dispatcher,CoroutineName等都是CoroutineContext的子类。
suspend
suspend就是一个提醒,用来告诉调用者这是一个耗时任务,需要在其他suspend方法或协程中调用
fun main() {
runBlocking {
launch {
printHello()
println("world thread: ${Thread.currentThread()}")
}
}
}
private suspend fun printHello() {
withContext(Dispatchers.IO) {
delay(1000)
println("hello, thread: ${Thread.currentThread()}")
}
}
输出结果:
hello, thread: Thread[DefaultDispatcher-worker-1,5,main]
world thread: Thread[main,5,main]
从输出结果得知,在协程中等待挂起函数结束之后才继续执行下边的操作,挂起函数我们切到IO线程执行,挂起函数执行完成之后自动恢复到刚才的位置,继续执行,所以我们可以使用挂起函数取代回调函数。
Job
调用launch函数创建协程时返回值就是Job,返回的Job代表刚创建的协程,Job有生命周期的概念,它会存在多种不同的状态。
public interface Job : CoroutineContext.Element {}
Job是一个接口,它是CoroutineContext.Element的子接口。
其他还有一些扩展函数,比如
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}
也都比较常用。
此外,还有CompletableJob,它带complete()方法,能主动完成Job。
任意子Job失败会导致此Job失败,并取消其他子Job,但SupervisorJob不会这样,SupervisorJob的子Job能独立的失败。
说到Job,就不得不一块说一下Deferred
Deferred
Deferred 是带返回值的Job
public interface Deferred<out T> : Job
await()方法可以等待Deferred结束并返回结果,但是请注意,此方法可能抛出CancellationException异常,在这两种情况下会抛出此异常。
- 调用await的协程被取消;
- Deferred获取完成结果是抛出CancellationException异常。
try {
deferred.await()
} catch (e: CancellationException) {
currentCoroutineContext().ensureActive() // throws if the current coroutine was cancelled
processException(e) // if this line executes, the exception is the result of `await` itself
}
调用此方法时我们可以添加try-catch
getCompleted()用来获取完成的结果,但是如果Deferred还没完成,此方法会抛出IllegalStateException异常。
同样,Deferred也有对应的CompletableDeferred,比较常用的方法就是complete,completeWith。
launch
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是CoroutineScope的扩展方法,它的返回值是Job,launch用来启动一个新的协程。
async
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
async同样是CoroutineScope的扩展方法,它的返回值是Deferred,async也用来启动一个新的协程。
withContext
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
// Copy CopyableThreadContextElement if necessary
val newContext = oldContext.newCoroutineContext(context)
// always check for cancellation of new context
newContext.ensureActive()
// FAST PATH #1 -- new context is the same as the old one
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
// `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
// There are changes in the context, so this thread needs to be updated
withCoroutineContext(coroutine.context, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// SLOW PATH -- use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont)
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}
withContext就是用来切线程的,它是一个挂起函数,它会在指定线程执行操作,挂起当前协程并返回结果。
private suspend fun printHello() {
withContext(Dispatchers.IO) {
delay(1000)
println("hello, thread: ${Thread.currentThread()}")
}
}
一般的用法是这样的。
coroutineScope
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
coroutineScope是一个挂起函数,它会创建一个新的协程作用域,但它不会启动协程,它会挂起当前协程,但不会阻塞所在的线程。
suspend fun showSomeData() = coroutineScope {
val data = async(Dispatchers.IO) { // <- extension on current scope
... load some UI data for the Main thread ...
}
withContext(Dispatchers.Main) {
doSomeWork()
val result = data.await()
display(result)
}
}
一般的用法是这样的。
作为对比,这里提一下supervisorScope()函数,coroutineScope中的任意子协程失败,会导致其他子协程取消,但supervisorScope不会这样,supervisorScope的子协程失败,不会导致其他子协程取消。
请阅读 kotlin协程
参考: