我们什么时候使用 launch(SupervisorJob())?
When do we use launch(SupervisorJob())?
我看过将 SupervisorJob
传递给 CoroutineScope
的教程,以避免在其中一个子程序失败时取消所有协程作业。
在run3
中,我认为将SupervisorJob
传递给launch
可以得到相同的结果,但显然不是。它似乎允许在出现异常时重用协程(如果您从 launch
中删除 SupervisorJob
,第二个 run2
调用将不会 运行 协程作业) ,但它的行为不像 supervisorScope
,它的其他子作业可以继续(在示例中是第一个 test1.run
调用)。我想知道在什么场景下我们可以使用这种方式?因为将它传递给 launch
构造函数看起来是合法的。
package coroutine.exceptions
import kotlinx.coroutines.*
fun log(msg: String) = println("$msg (${Thread.currentThread().name})")
val logExceptionHandler = CoroutineExceptionHandler { _, e ->
log(e.localizedMessage)
}
fun main() = runBlocking {
TestReuseCoroutineAfterException4("test1").run {
run1(true)
delay(100)
println()
run1(false)
delay(100)
}
println("================================================================")
TestReuseCoroutineAfterException4("test2").run {
run2(true)
delay(100)
println()
run2(false)
delay(100)
}
println("================================================================")
TestReuseCoroutineAfterException4("test3").run {
run3(true)
delay(100)
println()
run3(false)
delay(100)
println()
}
log("finished")
}
class TestReuseCoroutineAfterException4(
private val testName: String
) : CoroutineScope by CoroutineScope(CoroutineName(testName)) {
// by passing a Job, we can let the exception propagate to this coroutine scope instead of the
// root one, which allows us to reuse the root scope.
fun run1(throwException: Boolean) = launch(logExceptionHandler + Job()) {
val logPrefix = "$testName.run1:"
coroutineScope {
launch {
launch {
if (throwException)
throw RuntimeException("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
suspend fun run2(throwException: Boolean) {
val logPrefix = "$testName.run2:"
supervisorScope {
launch(logExceptionHandler) {
launch {
if (throwException)
throw Exception("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
// this will be run.
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
fun run3(throwException: Boolean) {
val logPrefix = "$testName.run3:"
launch(logExceptionHandler + SupervisorJob()) {
launch {
launch {
if (throwException)
throw Exception("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
// this will still be run.
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
}
输出
test1.run1: throw exception (DefaultDispatcher-worker-2 @test1#2)
test1.run1: done (job#1-1) (DefaultDispatcher-worker-2 @test1#7)
test1.run1: done (job#1-2) (DefaultDispatcher-worker-2 @test1#8)
test1.run1: done (job#1) (DefaultDispatcher-worker-2 @test1#6)
test1.run1: done (job#2) (DefaultDispatcher-worker-2 @test1#9)
================================================================
test2.run2: throw exception (main @coroutine#10)
test2.run2: done (job#2) (main @coroutine#12)
test2.run2: done (job#1-1) (main @coroutine#14)
test2.run2: done (job#1-2) (main @coroutine#15)
test2.run2: done (job#1) (main @coroutine#13)
test2.run2: done (job#2) (main @coroutine#16)
================================================================
test3.run3: throw exception (DefaultDispatcher-worker-2 @test3#18)
test3.run3: done (job#1-1) (DefaultDispatcher-worker-4 @test3#22)
test3.run3: done (job#1-2) (DefaultDispatcher-worker-4 @test3#23)
test3.run3: done (job#1) (DefaultDispatcher-worker-4 @test3#21)
test3.run3: done (job#2) (DefaultDispatcher-worker-4 @test3#24)
finished (main @coroutine#1)
Process finished with exit code 0
if you remove the SupervisorJob
from launch
, second run2
call won't run the coroutine job
此行为的原因不是您传递了 SupervisorJob
,而是您传递了 any 类型的 Job
。尝试用 + Job()
替换 + SupervisorJob()
,第二次调用 run2()
将执行协程。
主要区别在于,当您将显式作业传递给 launch
时,它会成为已启动协程的父作业,而不是 TestReuseCoroutineAfterException4
中的主作业。因此,协程失败不会取消主作业,并且影响仅限于单个调用。
不鼓励将作业直接传递给 launch
,因为它会破坏结构化并发并创建您遇到的奇怪语义。
我看过将 SupervisorJob
传递给 CoroutineScope
的教程,以避免在其中一个子程序失败时取消所有协程作业。
在run3
中,我认为将SupervisorJob
传递给launch
可以得到相同的结果,但显然不是。它似乎允许在出现异常时重用协程(如果您从 launch
中删除 SupervisorJob
,第二个 run2
调用将不会 运行 协程作业) ,但它的行为不像 supervisorScope
,它的其他子作业可以继续(在示例中是第一个 test1.run
调用)。我想知道在什么场景下我们可以使用这种方式?因为将它传递给 launch
构造函数看起来是合法的。
package coroutine.exceptions
import kotlinx.coroutines.*
fun log(msg: String) = println("$msg (${Thread.currentThread().name})")
val logExceptionHandler = CoroutineExceptionHandler { _, e ->
log(e.localizedMessage)
}
fun main() = runBlocking {
TestReuseCoroutineAfterException4("test1").run {
run1(true)
delay(100)
println()
run1(false)
delay(100)
}
println("================================================================")
TestReuseCoroutineAfterException4("test2").run {
run2(true)
delay(100)
println()
run2(false)
delay(100)
}
println("================================================================")
TestReuseCoroutineAfterException4("test3").run {
run3(true)
delay(100)
println()
run3(false)
delay(100)
println()
}
log("finished")
}
class TestReuseCoroutineAfterException4(
private val testName: String
) : CoroutineScope by CoroutineScope(CoroutineName(testName)) {
// by passing a Job, we can let the exception propagate to this coroutine scope instead of the
// root one, which allows us to reuse the root scope.
fun run1(throwException: Boolean) = launch(logExceptionHandler + Job()) {
val logPrefix = "$testName.run1:"
coroutineScope {
launch {
launch {
if (throwException)
throw RuntimeException("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
suspend fun run2(throwException: Boolean) {
val logPrefix = "$testName.run2:"
supervisorScope {
launch(logExceptionHandler) {
launch {
if (throwException)
throw Exception("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
// this will be run.
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
fun run3(throwException: Boolean) {
val logPrefix = "$testName.run3:"
launch(logExceptionHandler + SupervisorJob()) {
launch {
launch {
if (throwException)
throw Exception("$logPrefix throw exception")
else
log("$logPrefix done (job#1-1)")
}.join()
launch {
log("$logPrefix done (job#1-2)")
}.join()
log("$logPrefix done (job#1)")
}.join()
// this will still be run.
launch {
log("$logPrefix done (job#2)")
}.join()
}
}
}
输出
test1.run1: throw exception (DefaultDispatcher-worker-2 @test1#2)
test1.run1: done (job#1-1) (DefaultDispatcher-worker-2 @test1#7)
test1.run1: done (job#1-2) (DefaultDispatcher-worker-2 @test1#8)
test1.run1: done (job#1) (DefaultDispatcher-worker-2 @test1#6)
test1.run1: done (job#2) (DefaultDispatcher-worker-2 @test1#9)
================================================================
test2.run2: throw exception (main @coroutine#10)
test2.run2: done (job#2) (main @coroutine#12)
test2.run2: done (job#1-1) (main @coroutine#14)
test2.run2: done (job#1-2) (main @coroutine#15)
test2.run2: done (job#1) (main @coroutine#13)
test2.run2: done (job#2) (main @coroutine#16)
================================================================
test3.run3: throw exception (DefaultDispatcher-worker-2 @test3#18)
test3.run3: done (job#1-1) (DefaultDispatcher-worker-4 @test3#22)
test3.run3: done (job#1-2) (DefaultDispatcher-worker-4 @test3#23)
test3.run3: done (job#1) (DefaultDispatcher-worker-4 @test3#21)
test3.run3: done (job#2) (DefaultDispatcher-worker-4 @test3#24)
finished (main @coroutine#1)
Process finished with exit code 0
if you remove the
SupervisorJob
fromlaunch
, secondrun2
call won't run the coroutine job
此行为的原因不是您传递了 SupervisorJob
,而是您传递了 any 类型的 Job
。尝试用 + Job()
替换 + SupervisorJob()
,第二次调用 run2()
将执行协程。
主要区别在于,当您将显式作业传递给 launch
时,它会成为已启动协程的父作业,而不是 TestReuseCoroutineAfterException4
中的主作业。因此,协程失败不会取消主作业,并且影响仅限于单个调用。
不鼓励将作业直接传递给 launch
,因为它会破坏结构化并发并创建您遇到的奇怪语义。