Job 作为 CoroutineScope 和 launch 的参数有什么不同?

What is different Job as a argument at CoroutineScope and launch?

这两个代码运行完全一样。将 Job 放入 CoroutineScope 并启动有什么不同?

private val job = CoroutineScope(Dispatchers.Main).launch(start = CoroutineStart.LAZY) {
    for(i in 10 downTo 0) {
        Log.d("test", ": $i")
        delay(1000)
    }
}

CoroutineScope(Dispatchers.Main+job).launch{ }

CoroutineScope(Dispatchers.Main).launch(job) { }

看源码,两者好像没有太大区别。 operator fun plusdocumentation states

Returns a context containing elements from this context and elements from other context. The elements from this context with the same key as in the other one are dropped.

这解释了您的第一个测试是如何进行的。第二个,使用上下文参数调用 launch,调用 CoroutineScope.newCoroutineContext

Creates a context for the new coroutine. It installs Dispatchers.Default when no other dispatcher or ContinuationInterceptor is specified, and adds optional support for debugging facilities (when turned on).

并查看它的源代码:

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

我们可以看到它最终也使用了 operator fun plus

这里还有一些其他差异似乎可以忽略不计,但最终启动协程的上下文在您的两个测试中看起来是相同的。

从技术上讲,两者都会导致相同的行为,但要点是两者都不是使用 CoroutineScope() 工厂的好方法。这是写同样东西的惯用方式:

GlobalScope.launch(Dispatchers.Main+job) { ... }

如果这让您大吃一惊(“不要使用 GlobalScope!”),那是因为它应该——您的示例只是犯同样错误的另一种方式,还有更多冗长的代码。您构造一个 CoroutineScope 而不持有对它的引用,导致与 GlobalScope 单例完全相同的无限和不可取消范围。

此外,您使用作为实际协程句柄的 Job 实例的方式也是错误的:与协程范围关联的作业应该是从 Job()SupervisorJob()。它的唯一目的是作为取消整个作用域或检查其状态的中心点。