Open bennyhuo opened 5 years ago
多谢老师分享。
回答一下思考题:拦截器可以有多个吗?
答:拦截器应该只能设置一个吧, 毕竟 ContinuationInterceptor
是 CoroutineContext
集合中的一个 Key
了,后续通过 plus
添加的 ContinuationInterceptor
会覆盖之前的。
如果要实现 OkHttp
那样的多次 Intercept
,应该可以在 Continuation
做文章。
@ZhouGongZaiShi 多谢老师分享。
回答一下思考题:拦截器可以有多个吗?
答:拦截器应该只能设置一个吧, 毕竟ContinuationInterceptor
是CoroutineContext
集合中的一个Key
了,后续通过plus
添加的ContinuationInterceptor
会覆盖之前的。
如果要实现OkHttp
那样的多次Intercept
,应该可以在Continuation
做文章。
是的 一个拦截器,然后再弄一堆出来
有个疑问,Dispatchers.IO有什么特殊的地方,我通过打印的日志看到好像和Deafult是同一个线程池,你上面的文章说并实现了独立的队列和限制,还是不太理解
是同一个线程池,Default 的设计思路实际上是调度一些不怎么重的任务,而 IO 通常比较耗时,因此 IO 在调度的时候会根据需求创建和销毁线程。你可以看下文档,讲的挺清楚的
先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲 CombinedContext
时不是很明白,为什么要定义成 left: CoroutineContext
+ element: Element
这样的数据结构呢?CoroutineContext
本身不就能实现类似 Combined
的功能嘛?也没见过 Map
里有 CombinedMap
呀。一点粗浅的问题,望大佬解惑~
另外,1.1中这句「这里的 Job
实际上是对它的 companion object
的引用」也没理解,Job
不是下面这个接口的类型吗?怎么变成了是对它的 companion object
的引用呢?
还有,这句话「拦截器也是一个上下文的实现方向,拦截器可以左右你的协程的执行,同时为了保证它的功能的正确性,协程上下文集合永远将它放在最后面 ,这真可谓是天选之子了」没太理解,为什么协程上下文集合永远将它放在最后面就是天选之子呢?请大佬指点 🤔
@RubiTree 先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲
CombinedContext
时不是很明白,为什么要定义成left: CoroutineContext
+element: Element
这样的数据结构呢?CoroutineContext
本身不就能实现类似Combined
的功能嘛?也没见过Map
里有CombinedMap
呀。一点粗浅的问题,望大佬解惑~
这里说的是函数式的递归 List 定义。CoroutineContext
的递归定义在 CombinedContext
中体现,这个写法跟 scala 的 List
如出一辙。函数式语言不能迭代,所有的循环、遍历都是靠递归,因此每一个 List 本质上就是头元素和剩下的 List 部分,遍历的时候访问了第一个,以后再递归的访问剩下的 List,这样就可以实现遍历。CoroutineContext
这么定义的用意也正是如此。
@RubiTree 另外,1.1中这句「这里的
Job
实际上是对它的companion object
的引用」也没理解,Job
不是下面这个接口的类型吗?怎么变成了是对它的companion object
的引用呢?
Job
是一个接口,没错。但它还有一个 companion object
,如果没有明确定义名称的话,我们可以直接用接口名来引用 companion object
。去看下相关的定义很简单的。
@RubiTree 还有,这句话「拦截器也是一个上下文的实现方向,拦截器可以左右你的协程的执行,同时为了保证它的功能的正确性,协程上下文集合永远将它放在最后面 ,这真可谓是天选之子了」没太理解,为什么协程上下文集合永远将它放在最后面就是天选之子呢?请大佬指点 🤔
因为 CoroutineContext
的递归定义的特性,它与通常意义的递归定义正好反着来了,所以它实际上是从最后一个元素开始遍历的,任何时候对它的遍历都是最先访问最后一个元素,换言之访问最后一个元素的查找时间是 O(1),而对于整个上下文集合的查找,时间复杂度是 O(n),把拦截器放到最后,就是为了更快的拿到拦截器实例。为什么需要更快的拿到拦截器?因为协程每次挂起后恢复时都会读取拦截器来处理拦截逻辑,因此拦截器的访问是非常高频的,因此这实际上是一个性能优化。
@enbandari
@RubiTree 先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲
CombinedContext
时不是很明白,为什么要定义成left: CoroutineContext
+element: Element
这样的数据结构呢?CoroutineContext
本身不就能实现类似Combined
的功能嘛?也没见过Map
里有CombinedMap
呀。一点粗浅的问题,望大佬解惑~这里说的是函数式的递归 List 定义。
CoroutineContext
的递归定义在CombinedContext
中体现,这个写法跟 scala 的List
如出一辙。函数式语言不能迭代,所有的循环、遍历都是靠递归,因此每一个 List 本质上就是头元素和剩下的 List 部分,遍历的时候访问了第一个,以后再递归的访问剩下的 List,这样就可以实现遍历。CoroutineContext
这么定义的用意也正是如此。
感谢大佬解答!所以协程因为也是不希望使用迭代来遍历,所以采用这样的数据结构吧
您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30
Key
都是ContinuationInterceptor
suspend fun main() {
log(1)
val job = GlobalScope.launch(
context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
start = CoroutineStart.UNDISPATCHED
) {
log(2)
delay(1000)
log(3)
log(coroutineContext[ContinuationInterceptor])
}
log(4)
job.join()
log(5)
}
private class MyNewContinuationInterceptor1 : ContinuationInterceptor {
// companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>
override val key: CoroutineContext.Key<*> = ContinuationInterceptor
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
log("<MyContinuation1> $result")
thread(name = "MyMain1") {
continuation.resumeWith(result)
}
}
}
}
}
private class MyNewContinuationInterceptor2 : ContinuationInterceptor {
// companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>
override val key: CoroutineContext.Key<*> = ContinuationInterceptor
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
log("<MyContinuation2> $result")
thread(name = "MyMain2") {
continuation.resumeWith(result)
}
}
}
}
}
输出如下:
14:57:49:039 [main] 1
14:57:49:100 [main] 2
14:57:49:116 [main] 4
14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit)
14:57:50:117 [MyMain2] 3
14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9
14:57:50:118 [MyMain2] 5
这种情况我是按后添加的拦截器会覆盖之前的拦截器,Key
是一样的
Key
都是自定义的suspend fun main() {
log(1)
val job = GlobalScope.launch(
context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
start = CoroutineStart.UNDISPATCHED
) {
log(2)
delay(1000)
log(3)
log(coroutineContext[ContinuationInterceptor])
}
log(4)
job.join()
log(5)
}
private class MyNewContinuationInterceptor1 : ContinuationInterceptor {
companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>
override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
log("<MyContinuation1> $result")
thread(name = "MyMain1") {
continuation.resumeWith(result)
}
}
}
}
}
private class MyNewContinuationInterceptor2 : ContinuationInterceptor {
companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>
override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
log("<MyContinuation2> $result")
thread(name = "MyMain2") {
continuation.resumeWith(result)
}
}
}
}
}
输出如下:
15:05:51:374 [main] 1
15:05:51:433 [main] 2
15:05:51:451 [main] 4
15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit)
15:05:52:452 [MyMain1] 3
15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9
15:05:52:453 [MyMain1] 5
这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~
@guodongAndroid 您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30
第一种情况:两个拦截器的
Key
都是ContinuationInterceptor
suspend fun main() { log(1) val job = GlobalScope.launch( context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(), start = CoroutineStart.UNDISPATCHED ) { log(2) delay(1000) log(3) log(coroutineContext[ContinuationInterceptor]) } log(4) job.join() log(5) } private class MyNewContinuationInterceptor1 : ContinuationInterceptor { // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1> override val key: CoroutineContext.Key<*> = ContinuationInterceptor override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation1> $result") thread(name = "MyMain1") { continuation.resumeWith(result) } } } } } private class MyNewContinuationInterceptor2 : ContinuationInterceptor { // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2> override val key: CoroutineContext.Key<*> = ContinuationInterceptor override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation2> $result") thread(name = "MyMain2") { continuation.resumeWith(result) } } } } }
输出如下:
14:57:49:039 [main] 1 14:57:49:100 [main] 2 14:57:49:116 [main] 4 14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit) 14:57:50:117 [MyMain2] 3 14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9 14:57:50:118 [MyMain2] 5
这种情况我是按后添加的拦截器会覆盖之前的拦截器,
Key
是一样的第二种情况:两个拦截器的
Key
都是自定义的suspend fun main() { log(1) val job = GlobalScope.launch( context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(), start = CoroutineStart.UNDISPATCHED ) { log(2) delay(1000) log(3) log(coroutineContext[ContinuationInterceptor]) } log(4) job.join() log(5) } private class MyNewContinuationInterceptor1 : ContinuationInterceptor { companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1> override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1 override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation1> $result") thread(name = "MyMain1") { continuation.resumeWith(result) } } } } } private class MyNewContinuationInterceptor2 : ContinuationInterceptor { companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2> override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2 override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation2> $result") thread(name = "MyMain2") { continuation.resumeWith(result) } } } } }
输出如下:
15:05:51:374 [main] 1 15:05:51:433 [main] 2 15:05:51:451 [main] 4 15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit) 15:05:52:452 [MyMain1] 3 15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9 15:05:52:453 [MyMain1] 5
这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~
因为 CoroutineContext 的 plus 只处理 KEY 是 ContinuationInterceptor 的拦截器
@guodongAndroid 您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30
第一种情况:两个拦截器的
Key
都是ContinuationInterceptor
suspend fun main() { log(1) val job = GlobalScope.launch( context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(), start = CoroutineStart.UNDISPATCHED ) { log(2) delay(1000) log(3) log(coroutineContext[ContinuationInterceptor]) } log(4) job.join() log(5) } private class MyNewContinuationInterceptor1 : ContinuationInterceptor { // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1> override val key: CoroutineContext.Key<*> = ContinuationInterceptor override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation1> $result") thread(name = "MyMain1") { continuation.resumeWith(result) } } } } } private class MyNewContinuationInterceptor2 : ContinuationInterceptor { // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2> override val key: CoroutineContext.Key<*> = ContinuationInterceptor override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation2> $result") thread(name = "MyMain2") { continuation.resumeWith(result) } } } } }
输出如下:
14:57:49:039 [main] 1 14:57:49:100 [main] 2 14:57:49:116 [main] 4 14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit) 14:57:50:117 [MyMain2] 3 14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9 14:57:50:118 [MyMain2] 5
这种情况我是按后添加的拦截器会覆盖之前的拦截器,
Key
是一样的第二种情况:两个拦截器的
Key
都是自定义的suspend fun main() { log(1) val job = GlobalScope.launch( context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(), start = CoroutineStart.UNDISPATCHED ) { log(2) delay(1000) log(3) log(coroutineContext[ContinuationInterceptor]) } log(4) job.join() log(5) } private class MyNewContinuationInterceptor1 : ContinuationInterceptor { companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1> override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1 override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation1> $result") thread(name = "MyMain1") { continuation.resumeWith(result) } } } } } private class MyNewContinuationInterceptor2 : ContinuationInterceptor { companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2> override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2 override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { return object : Continuation<T> { override val context: CoroutineContext = continuation.context override fun resumeWith(result: Result<T>) { log("<MyContinuation2> $result") thread(name = "MyMain2") { continuation.resumeWith(result) } } } } }
输出如下:
15:05:51:374 [main] 1 15:05:51:433 [main] 2 15:05:51:451 [main] 4 15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit) 15:05:52:452 [MyMain1] 3 15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9 15:05:52:453 [MyMain1] 5
这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~
因为 CoroutineContext 的 plus 只处理 KEY 是 ContinuationInterceptor 的拦截器
上下文当中的key只能对应一个,这跟map是类似的。拦截器自然只会有一个生效。
你也可以看一下这期视频来了解更多:【[Kotlin 协程] 给协程的拦截器自定义 Key 有什么后果?-哔哩哔哩】 https://b23.tv/wZsWxEI
suspend fun main() {
log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程
val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
GlobalScope.launch(myDispatcher) {
log("2")
}.join()
log("3")
}
[main] 1
[MyThread] 2
[main] 3
bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。
suspend fun main() { log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程 val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher() GlobalScope.launch(myDispatcher) { log("2") }.join() log("3") }
[main] 1 [MyThread] 2 [main] 3
bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。
这个其实不是一定的。3在不在主线程,完全取决于join是不是会挂起。
@bennyhuo
suspend fun main() { log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程 val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher() GlobalScope.launch(myDispatcher) { log("2") }.join() log("3") }
[main] 1 [MyThread] 2 [main] 3
bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。
这个其实不是一定的。3在不在主线程,完全取决于join是不是会挂起。
感谢点拨,如果 join
挂起了,log(3)
所在的协程续体,由于没定义拦截器就会在 log(2)
的拦截器定义的线程里 resume
。
suspend fun main() {
log("1")
val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
GlobalScope.launch(myDispatcher) {
log("2")
delay(100)
}.join()
log("3") // log 3 始终是 MyThread 线程
}
关于: 使用自定义dispatcher(使用10个线程的线程池)例子中, delay 的前后 线程切换了。 但是我将自定义的dispatcher换成 Dispatcher.IO 或者 default, 发现 delay前后,线程并没有切换。 这个是为什么? IO 和 default 背后 也是 >1 的线程池啊
大于1,也可以调度到之前的线程,合理啊
---原始邮件--- 发件人: "Agera @.> 发送时间: 2023年4月27日(周四) 上午9:39 收件人: @.>; 抄送: "Benny @.**@.>; 主题: Re: [bennyhuo/Bennyhuo] 破解 Kotlin 协程(3) - 协程调度篇 | Bennyhuo (#17)
关于: 使用自定义dispatcher(使用10个线程的线程池)例子中, delay 的前后 线程切换了。 但是我将自定义的dispatcher换成 Dispatcher.IO 或者 default, 发现 delay前后,线程并没有切换。 这个是为什么? IO 和 default 背后 也是 >1 的线程池啊
— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>
fun main() = runBlocking {
e(0)
coroutineScope {
launch(Dispatchers.IO) {
e(1)
delay(100)
e(2)
}
}
e(3)
}
贴上一个打印数据:
---[main-1]---0
---[DefaultDispatcher-worker-1-13]---1
---[DefaultDispatcher-worker-1-13]---2
---[main-1]---3
像这个例子, 我运行了很多次, 1 和 2 都处于同一个线程。 Dispatchers.IO 背后的线程池的线程数 没记错的话 默认是64, 为什么 delay 前后还在同一个线程中呢?
我看老师的例子中,如果自己实现这个dispatcher (用10 个线程的线程池), delay前后 有可能分别在不同的线程。
但是如果我将此处的 Dispatcher.IO 换成是自己实现的 newFixedThreadPool(10).asCoroutineDispatcher() 后, delay 前后 又在不同的线程了, 比如下面:
fun main() = runBlocking {
e(0)
val dis = Executors.newFixedThreadPool(10).asCoroutineDispatcher()
coroutineScope {
launch(dis) {
e(1)
delay(100)
e(2)
}
}
e(3)
}
打印如下:
---[main-1]---0
---[pool-1-thread-1-13]---1
---[pool-1-thread-2-15]---2
---[main-1]---3
IO 仅在 Jvm 上有定义,它基于 Default 调度器背后的线程池,并实现了独立的队列和限制,因此协程调度器从 Default 切换到 IO 并不会触发线程切换。
下面这个 从 Default 切到 IO,线程是发生了变化啊
fun main() = runBlocking {
e(0)
coroutineScope {
launch(Dispatchers.Default) {
e(1)
launch(Dispatchers.IO) {
e(2)
}
}
}
e(3)
}
---[main-1]---0
---[DefaultDispatcher-worker-1-13]---1
---[DefaultDispatcher-worker-2-14]---2
---[main-1]---3
这是内部的调度机制决定的,如果想要了解细节,建议直接看源码。理论上,一直调度到一个线程也是不违反设计的,另外,并没有 64 个线程的说法,应该是最多使用 64 个线程处理并发调度(实际上要受限于线程池的大小)。
猜测,这里始终使用一个线程,可能跟线程池比较闲有关系,你可以试试同时调度点儿别的任务。
另外需要说明一点的是,文章是基于 Kotlin 1.3 撰写的,版本变化可能会带来结果的差异,是正常的。
https://www.bennyhuo.com/2019/04/11/coroutine-dispatchers/
上一篇我们知道了协程启动的几种模式,也通过示例认识了
launch
启动协程的使用方法,本文将延续这些内容从调度的角度来进一步为大家揭示协程的奥义。