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
}
/**
* A global [CoroutineScope] not bound to any job.
*
* Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
* and are not cancelled prematurely.
* Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
*
* Application code usually should use an application-defined [CoroutineScope]. Using
* [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
* on the instance of [GlobalScope] is highly discouraged.
*
* Usage of this interface may look like this:
*
* ```
* fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
* for (number in this) {
* send(Math.sqrt(number))
* }
* }
* ```
*/
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
您还可以将其用作
class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
println("🤬 Exception $throwable in context:$coroutineContext")
}
private val dataBinding by lazy {
Activity3CoroutineLifecycleBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(dataBinding.root)
job = Job()
dataBinding. button.setOnClickListener {
// This scope lives as long as Application is alive
GlobalScope.launch {
for (i in 0..300) {
println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
delay(300)
}
}
// This scope is canceled whenever this Activity's onDestroy method is called
launch {
for (i in 0..300) {
println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
withContext(Dispatchers.Main) {
dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
}
delay(300)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
4条答案
按热度按时间yhived7q1#
首先,让我们从定义开始,如果你需要一个关于协同程序和协同程序流的教程或游戏场,你可以看看我创建的这个tutorial/playground。
Scope
是用于启动只包含一个对象CoroutineContext
的协程的对象协程上下文是一组规则和配置,定义了协程将如何执行。在幕后,它是一种Map,具有一组可能的键和值。
协程上下文是不可变的,但是您可以使用加号运算符向上下文添加元素,就像向集合添加元素一样,生成一个新的上下文示例
定义协程行为的一组元素是:
调度程序调度程序确定应使用哪个线程池。调度程序类也是CoroutineContext,可以将其添加到CoroutineContext
*调度程序。默认值:CPU密集型的工作,如排序大列表,做复杂的计算和类似的。JVM上的共享线程池支持它。
*调度员。IO:联网或从文件中阅读。简而言之-任何输入和输出,如名称所述
*调度程序。主要:用于在Android主线程或UI线程中执行UI相关事件的强制调度程序。
例如,在RecyclerView中显示列表、更新视图等。
你可以查看Android的官方文件,了解更多关于调度员的信息。
[编辑]尽管官方文件声明
这个调度程序被优化为在主线程之外执行磁盘或网络I/O。示例包括使用Room组件、阅读或写入文件以及运行任何网络操作。
Marko Topolnic的答复
IO在一个特殊的、灵活的线程池中运行协程,它只是在你被迫使用一个遗留的、阻塞的IO API来阻塞它的调用线程时作为一种解决方案而存在。
也可能是对的。
作业协同程序本身由作业表示。作业是协同程序的句柄。对于您创建的每个协同程序(通过启动或异步),它返回一个作业示例,该示例唯一标识协同程序并管理其生命周期。您还可以将作业传递给协同程序作用域以保留其生命周期的句柄。
它负责协同程序的生命周期、取消和父子关系,可以从当前协同程序的上下文中检索当前作业:作业可以经历一组状态:新建、活动、正在完成、已完成、正在取消和已取消。虽然我们无法访问状态本身,但我们可以访问作业的属性:活动、取消和完成。
CoroutineScope它被定义为一个简单的工厂函数,该函数将
CoroutineContext
s作为参数,以在组合的CoroutineContext周围创建 Package 器,如并且如果提供的上下文还没有X1 M3 N1 X元素,则创建X1 M3 N1 X元素。
我们来看一下GlobalScope源代码
如您所见,它扩展了
CoroutineScope
1-
GlobalScope.launch(Dispatchers.IO) {}
GlobalScope只要您的应用程序处于活动状态就处于活动状态,如果您在此范围内进行一些计数并旋转您的设备,它将继续执行任务/过程。只要您的应用处于活动状态就运行,但由于使用
Dispatchers.IO
,因此在IO线程中运行2-
GlobalScope.launch{}
与第一个相同,但默认情况下,如果您没有任何上下文,启动使用EmptyCoroutineContext,它使用Dispatchers。默认,因此唯一的区别是线程与第一个。3-
CoroutineScope(Dispatchers.IO).launch{}
这个与第一个相同,只是语法不同。4-
lifecycleScope.launch(Dispatchers.IO){}
lifecycleScope
是LifeCycleOwner
的扩展,绑定到活动或片段的生命周期,当活动或片段被销毁时,作用域被取消。您还可以将其用作
sg2wtvxw2#
靶区; DR
1.* * 全局范围.启动(调度程序. IO):在
Dispatchers.IO
上启动顶级协同程序。协同程序未绑定并一直运行,直到完成或取消。通常不鼓励这样做,因为程序员必须维护对join()
或cancel()
的引用。1.* * 全球范围.启动:同上,但
GlobalScope
在未指定时使用Dispatchers.Default
。通常不建议使用。1.* * 协同程序范围(调度程序. IO).启动**:创建一个使用
Dispatchers.IO
的协程作用域,除非在协程生成器中指定了调度程序,即launch
1.* * 协同程序范围(调度程序. IO).启动(调度程序.主):额外的好处是,使用与上面相同的协程作用域(如果作用域示例相同!),但是对于这个协程,用
Dispatchers.Main
覆盖Dispatcher.IO
。1.* * 生命周期范围.启动(调度程序. IO):在AndroidX提供的lifecycleScope内启动协同程序。一旦生命周期失效(即用户导航离开片段),协同程序将被取消。使用
Dispatchers.IO
作为线程池。1.* * 生命周期范围.启动**:同上,但如果未指定,则使用
Dispatchers.Main
。解释
join()
或cancel()
的引用。launch {}
)要使用哪个线程池。Dispatchers.Default
-使用与CPU内核数相等的线程池。应用于CPU限制的工作负载。Dispatchers.IO
-使用64个线程的池。非常适合IO受限的工作负载,其中线程通常处于等待状态;可能用于网络请求或磁盘读/写。Dispatchers.Main
(仅限安卓系统):使用主线程来执行协程。非常适合更新UI元素。示例
我已经写了一个小的演示片段与6个功能对应上述6个场景。如果你运行下面的片段在Android设备;打开碎片并留下碎片;你会注意到只有GlobalScope协程仍然有效。当生命周期无效时,生命周期协程会被lifecycleScope取消。另一方面,在
onPause()
调用时,我们会显式地取消CoroutineScope协程。yhxst69z3#
我会把你的清单沿着三个部分:
GlobalScope
对比CoroutineScope()
对比lifecycleScope
Dispatchers.IO
与继承的(隐式)调度程序1.将作用域vs.中的调度程序指定为
launch
的参数1.范围选择
Kotlin对协程的理解很大一部分是“结构化并发”,这意味着所有的协程都被组织成一个层次结构,遵循它们的依赖关系。如果你正在启动一些后台工作,我们假设你希望它的结果在某个时候出现,而当前的“工作单元”仍然是活动的,也就是说,用户还没有离开它,也不再关心它的结果。
在Android上,您可以使用
lifecycleScope
,它会自动跟随用户在UI Activity之间的导航,因此您应该将其用作后台工作的父级,用户可以看到后台工作的结果。你也可能有一些一次性的工作,你只需要最终完成,但用户不会等待它的结果。为此,你应该使用Android的
WorkManager
或类似的功能,即使用户切换到另一个应用程序,也可以安全地继续。这些通常是同步你的本地状态与服务器端保持的状态的任务。在这幅图中,
GlobalScope
基本上是结构化并发的一个逃生出口,它允许你满足提供作用域的形式,但是破坏了它应该实现的所有机制。GlobalScope
永远不能被取消,并且它没有父对象。编写
CoroutineScope(...).launch
是错误的,因为你创建了一个没有父对象的作用域对象,而父对象很快就会忘记,因此无法取消它,这类似于使用GlobalScope
,但更加笨拙。2.调度程序的选择
协程调度器决定你的协程可以在哪些线程上运行。在Android上,有三个调度器你应该关心:
Main
在单个GUI线程上运行所有内容,它应该是您的主要选择。IO
在一个特殊的、灵活的线程池上运行协程。它仅在您被迫使用会阻塞其调用线程的旧式阻塞IO API时作为一种变通方案存在。Default
也使用线程池,但大小固定,等于CPU核心数。将其用于计算密集型工作,这些工作需要的时间长到足以导致GUI出现故障(例如,图像压缩/解压缩)。3.指定调度程序的位置
首先,您应该知道您正在使用的协程作用域中指定的调度器。
GlobalScope
没有指定任何调度器,因此一般默认值是Default
调度器。lifecycleScope
指定Main
调度器。我们已经解释过,不应该使用
CoroutineScope
构造函数创建ad-hoc作用域,因此指定显式调度程序的适当位置是作为launch
的参数。在技术细节上,当你写
someScope.launch(someDispatcher)
时,someDispatcher
参数实际上是一个成熟的协程上下文对象,它碰巧有一个元素,调度器。你启动的协程通过组合协程作用域中的一个和你作为参数提供的一个为自己创建一个新的上下文。它为自己创建一个新的Job
并将其添加到上下文中。该作业是在上下文中继承的作业的子作业。omvjsjqw4#
你应该知道,如果你想启动
suspend
函数,你需要在CoroutineScope
中执行它。每个CoroutineScope
都有CoroutineContext
。其中CoroutineContext
是一个可以包含Dispatcher
的Map(将工作分派给适当的线程),Job
(控制协程的生命周期)、CoroutineExceptionHandler
(处理未捕获的异常)、CoroutineName
(协程的名称,用于调试)。GlobalScope.launch(Dispatchers.IO) {}
-GlobalScope.launch
创建全局协程,并用于不应取消的操作,但更好的替代方法是在Application类中创建一个自定义作用域,并将其注入到需要它的类中。这样做的好处是,您可以使用CoroutineExceptionHandler
或替换CoroutineDispatcher
进行测试。GlobalScope.launch{}
-与GlobalScope.launch(Dispatchers.IO) {}
相同,但在Dispatchers.Default
上运行coroutines
。Dispatchers.Default
是默认的Dispatcher
,在其上下文中未指定调度程序时使用。CoroutineScope(Dispatchers.IO).launch{}
-它使用一个参数创建作用域,并在IO
线程上在其中启动新的coroutine
。将与启动对象一起销毁。但如果要正确结束工作,应手动为CoroutineScope
调用.cancel()
。lifecycleScope.launch(Dispatchers.IO){}
-Lifecycle
或LifecycleOwner
中可用的现有范围(Activity
或Fragment
),并在您的项目中附带依赖项androidx.lifecycle:lifecycle-runtime-ktx:*
。使用它,您可以摆脱手动创建CoroutineScope
。它将在Dispatchers.IO
中运行您的作业,而不会阻塞MainThread
,并确保当您的lifecycle
被销毁时,您的作业将被取消。lifecycleScope.launch{}
-与lifecycleScope.launch(Dispatchers.IO){}
相同,它使用默认Dispatchers.Main
参数为您创建CoroutinesScope
,并在Dispatcher.Main
中运行coroutines
,这意味着您可以使用UI
。