Kotlin协程作用域与构建器详解
CoroutineScope.launch{}:
- CoroutineScope.launch{}是最常用的Coroutine builders,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器。
runBlocking{}:
- runBlocking{}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的。
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
//CoroutineScope.launch{}是最常用的Coroutine builders,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器。
GlobalScope.launch {
delay(1000)
println("Kotlin Coroutines")
}
println("Hello")
//runBlocking{}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的。
runBlocking {
delay(2000)
}
println("World")
}
RUN> 🚗🚗🚗🚗🚗🚗
Hello Kotlin Coroutines World Process finished with exit code 0
runBlocking { delay(2000) }
它会阻塞当前线程,当然也就相当于之前写的sleep效果
CoroutineScope
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
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
}
image-20210611113549654
The best ways to obtain a standalone instance of the scope are CoroutineScope() and MainScope() factory functions. Additional context elements can be appended to the scope using the plus operator.
Convention for structured concurrency
获取一个单独范围的实例的最佳方式是通过CoroutineScope()和MainScope()工厂函数。
额外的上下文元素可以通过用plus操作符将其添加到指定范围。
Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead. By convention, the context of a scope should contain an instance of a job to enforce the discipline of structured concurrency with propagation of cancellation.
手动来实现该接口是不被推荐的,而采用委托的方式来实现则是优先选择的。
按照约定,scope的上下文需要包含一个job的实例来强制进行结构化的并化。
Every coroutine builder (like launch, async, etc) and every scoping function (like coroutineScope, withContext, etc) provides its own scope with its own Job instance into the inner block of code it runs. By convention, they all wait for all the coroutines inside their block to complete before completing themselves, thus enforcing the structured concurrency. See Job documentation for more details.
CoroutineScope必须被实现或者用作一个字段在具有良好定义生命周期的实体上,用于启动子协程。
像这种在Android上用的实体的示例典型的就是Activity,
这个接口的使用长这样
继续再来分析一下lauch方法:
public fun CoroutineScope.launch(//launch 他是CoroutineScope中的扩展方法
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
//定义了三个参数,前两个参数都有默认值,只有第三个参数是一个Lambda表达式,所以我们在调用它时可以将代码写在{}里面
): Job {//返回一个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
}
其中该方法用到了一个我们还木有学到的关键字:
image-20210611114413054好下面正式来读一读它的javadoc:
image-20210611114450904Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job. The coroutine is cancelled when the resulting job is cancelled.
它会启动一个新的协程,并且它不会阻塞当前线程,会返回-个协程的引用比如一个Job。
当返回的Job被取消时则协程也会被取消
The coroutine context is inherited from a CoroutineScope. Additional context elements can be specified with context argument. If the context does not have any dispatcher nor any other ContinuationInterceptor, then Dispatchers.Default is used. The parent job is inherited from a CoroutineScope as well, but it can also be overridden with a corresponding context element.
协程上下文是继承于CoroutineScope。额外的上下文元素能够被context参数所指定。
如果这个context参数没有指定任何dispatcher也没有任何其它的ContinualtionInterceptor,那么Dispatchers.Default就会被使用。
父Job也会从CoroutineScope来继承,但是它能够被指定的cocontext所署盖.
By default, the coroutine is immediately scheduled for execution. Other start options can be specified via start parameter. See CoroutineStart for details. An optional start parameter can be set to CoroutineStart.LAZY to start coroutine lazily. In this case, the coroutine Job is created in new state. It can be explicitly started with start function and will be started implicitly on the first invocation of join.
默认情况下,协程会立马被调度去执行。另外启动选项还可以通过设置start参数来指定。可以参看CoroutineStart来查看详情。
一个start参数的可以被设置成LAZY用来延迟的来启动协程,此时该协程的Job就会被创建成个new的状态。
它可以通过显示调用start函数被启动,并且隐式的在join被第次调用时进行启动
Uncaught exceptions in this coroutine cancel the parent job in the context by default (unless CoroutineExceptionHandler is explicitly specified), which means that when launch is used with the context of another coroutine, then any uncaught exception leads to the cancellation of the parent coroutine.
在协程中未被捕获的异常默认情况下会取消在这个上下文下的父,job (除非显示的指定了CoroutineExceptionHandler),这就意味着当用另一个协程上下文进行启动时,任务未被捕获的异常都会导致父协程的取消。
CoroutineStart枚举类的说明
Defines start options for coroutines builders. It is used in start parameter of launch, async, and other coroutine builder functions.
The summary of coroutine start options is:
DEFAULT -- immediately schedules coroutine for execution according to its context;
LAZY -- starts coroutine lazily, only when it is needed;
ATOMIC -- atomically (in a non-cancellable way) schedules coroutine for execution according to its context;
UNDISPATCHED -- immediately executes coroutine until its first suspension point in the current thread.
DEFAULT:会根据其上下文立马去调度协程去执行。
LAZY:延迟的启动协程,只有当协程需要时。
ATOMIC:根据其上下文原子的(以一个不可以取消的方式)调度协程去执行。
UNDISPATCHED:立刻去执行协程直到遇到当前线程第一个挂起点
runBlocking
runBlocking{}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的。
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking { //直接将runBlocking函数返回给main函数
GlobalScope.launch {
delay(1000)
println("Kotlin Coroutines")
}
println("Hello")
delay(2000)
println("World")
}
在最后抛出一个问题?有木有一种机制能够精确的等待协程执行完了,再执行主线程呢?目前我们的做法不太精确,就是定了一个休眠时间比协程长
delay(2000)
答案肯定是有的,如果是要等某个线程执行完可以用join(),但协程如何等待呢?咱们下次再揭晓。
网友评论