我一直是kotlin docs如果我理解正确,两个kotlin函数的工作方式如下:
withContext(context)
:切换当前协同程序的上下文,当给定的块执行时,协同程序切换回上一个上下文。async(context)
:在给定的上下文中启动一个新的协同程序,如果我们在返回的await()
任务上调用Deferred
,它将挂起调用协程并在块内执行时恢复催生了coroutine返回。现在有以下两个版本的code
:
版本1:
launch(){
block1()
val returned = async(context){
block2()
}.await()
block3()
}
版本2:
launch(){
block1()
val returned = withContext(context){
block2()
}
block3()
}
我的问题是:
使用withContext
而不是asynch-await
并不总是更好,因为它在功能上相似,但不会创建另一个协同程序。大型数字协程,虽然轻量级仍然是要求苛刻的应用程序中的问题
是否有asynch-await
更优先于withContext
更新
Kotlin 1.2.50现在有代码检查,可以转换async(ctx) { }.await() to withContext(ctx) { }
。
答案 0 :(得分:72)
大量的协同程序虽然很轻,但在要求苛刻的应用程序中仍然可能存在问题
我想消除这个"太多的协同程序"通过量化他们的实际成本来解决问题。
首先,我们应该将 coroutine 本身从它附加的协程上下文中解开。这就是你如何以最小的开销创建一个协程:
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
的两倍,但它仍然只是一微秒。你必须在一个紧密的循环中启动它们,除此之外几乎什么都不做,因为它成为了一个问题&#34;在你的应用程序中。
使用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
- 这就是使用它的方式。答案 1 :(得分:9)
使用withContext而不是asynch-await总是更好,因为它功能相似,但不会创建另一个协同程序。大型的协同程序,虽然轻量级仍然是要求苛刻的应用程序的问题
是否有一种情况asynch-await比withContext
更可取
如果要同时执行多个任务,则应使用async / await,例如:
runBlocking {
val deferredResults = arrayListOf<Deferred<String>>()
deferredResults += async {
delay(1, TimeUnit.SECONDS)
"1"
}
deferredResults += async {
delay(1, TimeUnit.SECONDS)
"2"
}
deferredResults += async {
delay(1, TimeUnit.SECONDS)
"3"
}
//wait for all results (at this point tasks are running)
val results = deferredResults.map { it.await() }
println(results)
}
如果您不需要同时运行多个任务,则可以使用withContext。
答案 2 :(得分:4)
如有疑问,请牢记以下原则:
如果必须并行执行多个任务,而最终结果取决于所有任务的完成,请使用async
。
要返回单个任务的结果,请使用withContext
。