- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我一直在阅读 kotlin docs ,如果我理解正确,这两个 Kotlin 函数的工作方式如下:
withContext(context)
:切换当前协程的上下文,当给定 block 执行时,协程切换回之前的上下文。async(context)
:在给定的上下文中启动一个新的协程,如果我们在返回的 Deferred
任务上调用 .await()
,它会暂停调用协程,并在生成的协程内执行的 block 返回时恢复。下面两个版本的code
:
版本 1:
launch(){
block1()
val returned = async(context){
block2()
}.await()
block3()
}
版本 2:
launch(){
block1()
val returned = withContext(context){
block2()
}
block3()
}
我的问题是:
使用 withContext
而不是 async-await
不是更好吗,因为它在功能上相似,但不会创建另一个协程。大量的协程虽然是轻量级的,但在要求苛刻的应用程序中仍然可能是一个问题。
有没有一种情况 async-await
比 withContext
更可取?
更新: Kotlin 1.2.50现在有一个代码检查,它可以将 async(ctx) { }.await() 转换为 withContext(ctx) { }
。
最佳答案
Large number of coroutines, though lightweight, could still be a problem in demanding applications
我想通过量化它们的实际成本来消除“太多协程”成为问题的神话。
首先,我们应该将 coroutine 本身与它所附加的 coroutine context 分开。这就是你如何以最小的开销创建一个协程:
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {
continuations.add(it)
}
}
这个表达式的值是一个 Job
持有一个挂起的协程。为了保留延续,我们将其添加到更大范围的列表中。
我对这段代码进行了基准测试,得出的结论是它分配了 140 字节 并需要 100 纳秒 才能完成。这就是协程的轻量级。
为了重现性,这是我使用的代码:
fun measureMemoryOfLaunch() {
val continuations = ContinuationList()
val jobs = (1..10_000).mapTo(JobList()) {
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {
continuations.add(it)
}
}
}
(1..500).forEach {
Thread.sleep(1000)
println(it)
}
println(jobs.onEach { it.cancel() }.filter { it.isActive})
}
class JobList : ArrayList<Job>()
class ContinuationList : ArrayList<Continuation<Unit>>()
这段代码启动了一堆协程,然后进入休眠状态,因此您有时间使用 VisualVM 等监控工具分析堆。我创建了专门的类 JobList
和 ContinuationList
因为这样可以更容易地分析堆转储。
为了获得更完整的故事,我使用下面的代码也测量了 withContext()
和 async-await
的成本:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import kotlin.coroutines.suspendCoroutine
import kotlin.system.measureTimeMillis
const val JOBS_PER_BATCH = 100_000
var blackHoleCount = 0
val threadPool = Executors.newSingleThreadExecutor()!!
val ThreadPool = threadPool.asCoroutineDispatcher()
fun main(args: Array<String>) {
try {
measure("just launch", justLaunch)
measure("launch and withContext", launchAndWithContext)
measure("launch and async", launchAndAsync)
println("Black hole value: $blackHoleCount")
} finally {
threadPool.shutdown()
}
}
fun measure(name: String, block: (Int) -> Job) {
print("Measuring $name, warmup ")
(1..1_000_000).forEach { block(it).cancel() }
println("done.")
System.gc()
System.gc()
val tookOnAverage = (1..20).map { _ ->
System.gc()
System.gc()
var jobs: List<Job> = emptyList()
measureTimeMillis {
jobs = (1..JOBS_PER_BATCH).map(block)
}.also { _ ->
blackHoleCount += jobs.onEach { it.cancel() }.count()
}
}.average()
println("$name took ${tookOnAverage * 1_000_000 / JOBS_PER_BATCH} nanoseconds")
}
fun measureMemory(name:String, block: (Int) -> Job) {
println(name)
val jobs = (1..JOBS_PER_BATCH).map(block)
(1..500).forEach {
Thread.sleep(1000)
println(it)
}
println(jobs.onEach { it.cancel() }.filter { it.isActive})
}
val justLaunch: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {}
}
}
val launchAndWithContext: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
withContext(ThreadPool) {
suspendCoroutine<Unit> {}
}
}
}
val launchAndAsync: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
async(ThreadPool) {
suspendCoroutine<Unit> {}
}.await()
}
}
这是我从上述代码中得到的典型输出:
Just launch: 140 nanoseconds
launch and withContext : 520 nanoseconds
launch and async-await: 1100 nanoseconds
是的,async-await
花费的时间大约是 withContext
的两倍,但它仍然只是一微秒。您必须在一个紧密的循环中启动它们,除此之外几乎什么都不做,这会成为您应用中的“问题”。
使用 measureMemory()
我发现每次调用的内存成本如下:
Just launch: 88 bytes
withContext(): 512 bytes
async-await: 652 bytes
async-await
的成本正好比 withContext
高 140 字节,我们得到的数字是一个协程的内存权重。这只是设置 CommonPool
上下文的全部成本的一小部分。
如果性能/内存影响是决定 withContext
和 async-await
之间的唯一标准,那么结论必须是在 99 中它们之间没有相关差异实际用例的百分比。
真正的原因是withContext()
是一个更简单直接的API,尤其是在异常处理方面:
async { ... }
中未处理的异常会导致其父作业被取消。无论您如何处理来自匹配的 await()
的异常,都会发生这种情况。如果你没有为它准备一个 coroutineScope
,它可能会拖垮你的整个应用程序。withContext { ... }
中未处理的异常只会被 withContext
调用抛出,您可以像处理任何其他异常一样处理它。withContext
也恰好进行了优化,利用了您暂停父协程并等待子协程这一事实,但这只是一个额外的好处。
async-await
应该保留给那些你真正需要并发的情况,以便你在后台启动几个协程,然后才等待它们。简而言之:
async-await-async-await
— 不要那样做,使用 withContext-withContext
async-async-await-await
——这就是使用它的方式。关于Kotlin:withContext() 与 Async-await,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50230466/
目前我的代码看起来像这样我有一个 ViewModel调用存储库进行一些后台计算并返回结果。 ViewModel 函数使用 viewModelScope.launch(Dispatchers.IO) 运
我有以下代码: import kotlinx.coroutines.* fun main() = runBlocking { launch { println("in sub
documentation of withContext 状态 Calls the specified suspending block with a given coroutine context,
问题陈述 我想将 HTTP 请求的生命周期与在 Web 应用程序范围之外创建的上下文相关联。因此,我编写了以下中间件(使用 github.com/go-chi/chi): func BindConte
在我们的应用程序中有很多查询,现在我们正在使用 ROOM ,我想确认在我们这样使用之前使用 Coroutine 的正确方法是什么 在道中 @Query("SELECT * FROM VISITS"
在我的服务中,我需要调用 onStartCommand一些需要withContext(Dispatchers.IO)的方法而是 CoroutineScope(Dispatchers.IO)喜欢: ur
我有一个用 Kotlin 编写的个人项目,我养成了非常慷慨地使用 withContext(...) 的习惯。在调用可能与 I/O 相关的任何内容时,我倾向于使用 withContext(Dispatc
我的 Android 应用程序需要在后台(在服务内)进行一些文件读/写,首先我使用: CoroutineScope(Dispatchers.IO).launch { val fos = ope
我一直在寻找一种在 Jasmine 中使失败消息更具描述性的方法,并发现了此功能 https://jasmine.github.io/api/edge/matchers.html#withContex
我一直在阅读 kotlin docs ,如果我理解正确,这两个 Kotlin 函数的工作方式如下: withContext(context):切换当前协程的上下文,当给定 block 执行时,协程切换
想了解一下kotlin协程的执行顺序和线程切换。我用了withContext切换到另一个上下文并运行耗时的任务,因此主线程不会被阻塞。但是 kotlin 并没有像预期的那样切换上下文。 代码在 kot
我想知道 withContext和 suspendCoroutine是挂起函数,除了 suspendCoroutine 提供了一个延续,所以如果我们使用 suspendCancellableCorou
我在下面有 3 个片段 只是范围发射 fun main() = CoroutineScope(Dispatchers.IO).launch { runMe() } fun rumMe(
我正在学习 Kotlin 的协程。 以下内容来自文章https://developer.android.com/kotlin/coroutines . 重要提示:使用 suspend 不会告诉 Kot
要更改函数中的线程,我使用 CoroutineScope 或 withContext。我不知道有什么区别,但是使用 CourineScope 我也可以使用处理程序。 例子: private fun r
想象一下,我有一个使用 Kotlin 实现的 Spring WebFlux Controller ,它看起来像这样: @RestController @RequestMapping("/api/foo
我正在使用下面的脚本创建一个协程: fun bar(completion: () -> Unit) { GlobalScope.launch(Dispatchers.IO) { val l
我是一名优秀的程序员,十分优秀!