首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Kotlin: withContext()与异步等待

Kotlin: withContext()与异步等待
EN

Stack Overflow用户
提问于 2018-05-08 09:35:56
回答 3查看 35.9K关注 0票数 123

我一直在阅读科特林博士,如果我正确地理解了这两个Kotlin函数,工作如下:

  1. withContext(context):切换当前协同线的上下文,当给定块执行时,协同线切换回以前的上下文。
  2. async(context):在给定的上下文中启动一个新的协同线,如果我们在返回的Deferred任务上调用.await(),它将挂起调用的协同线,并在派生的coroutine中执行的块返回时继续运行。

现在,对于以下两个版本的code

Version1:

代码语言:javascript
复制
  launch(){
    block1()
    val returned = async(context){
      block2()
    }.await()
    block3()
  }

Version2:

代码语言:javascript
复制
  launch(){
    block1()
     val returned = withContext(context){
      block2()
    }
    block3()
  }
  1. 在两个版本的block1()中,block3()都在默认上下文中执行(公共池?)当block2()在给定的上下文中执行时。
  2. 总体执行与block1() -> block2() -> block3()顺序同步。
  3. 我看到的唯一不同是,version1创建了另一个协同线,因为version2在切换上下文时只执行一个协同线。

我的问题是:

  1. 使用withContext不是总是比使用async-await更好吗,因为它在功能上是相似的,但不会创建另一个协同机制。大量的协同服务,尽管是轻量级的,但在要求应用程序时仍然是一个问题。
  2. 是否有async-awaitwithContext更可取的情况?

更新: 科特林1.2.50现在有一个可以转换async(ctx) { }.await() to withContext(ctx) { }的代码检查。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-05-08 10:14:31

大量协同工作,虽然很轻量级,但在要求应用程序时仍然是一个问题。

我想通过量化它们的实际成本来消除“太多的合作”是一个问题的神话。

首先,我们应该将协同线本身与它所附的协同线上下文分开。这就是如何创建一个开销最小的协同线:

代码语言:javascript
复制
GlobalScope.launch(Dispatchers.Unconfined) {
    suspendCoroutine<Unit> {
        continuations.add(it)
    }
}

这个表达式的值是一个Job,它包含一个挂起的协同线。为了保留延续,我们将其添加到范围更广的列表中。

我对这段代码进行了基准测试,得出的结论是,它分配140个字节,并花费100纳秒来完成。所以这是一个轻量级的协同线。

为了重现性,我使用了以下代码:

代码语言:javascript
复制
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这样的监视工具来分析堆。我创建了专门的类JobListContinuationList,因为这使得分析堆转储变得更容易。

为了获得更完整的故事,我使用了下面的代码来度量withContext()async-await的成本

代码语言:javascript
复制
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()
    }
}

这是我从上面的代码中得到的典型输出:

代码语言:javascript
复制
Just launch: 140 nanoseconds
launch and withContext : 520 nanoseconds
launch and async-await: 1100 nanoseconds

是的,async-await花费的时间大约是withContext的两倍,但它仍然只有一微秒。你必须在一个紧密的循环中启动它们,几乎什么也不做,这样你的应用程序就会变成“一个问题”。

使用measureMemory(),我发现每个调用的内存成本如下:

代码语言:javascript
复制
Just launch: 88 bytes
withContext(): 512 bytes
async-await: 652 bytes

async-await的成本正好比withContext高140个字节,这是我们作为一个协同线的内存重量得到的数字。这只是设置CommonPool上下文的全部成本的一小部分。

如果性能/内存影响是决定withContextasync-await之间的唯一标准,那么得出的结论是,在99%的实际用例中,它们之间没有相关的差别。

真正的原因是withContext()是一个更简单、更直接的API,特别是在异常处理方面:

  • async { ... }中未处理的异常将导致其父作业被取消。无论您如何处理匹配的await()的异常,都会发生这种情况。如果您没有为它准备一个coroutineScope,它可能会使您的整个应用程序瘫痪。
  • withContext { ... }中没有处理的异常只会被withContext调用抛出,您就像处理其他任何异常一样处理它。

withContext也进行了优化,利用了暂停父协同和等待孩子的事实,但这只是一个额外的奖励。

应该为实际需要并发性的情况保留async-await,以便在后台启动几个协同线,然后等待它们。简言之:

  • async-await-async-await -不要那样做,使用withContext-withContext
  • async-async-await-await --这就是使用它的方法。
票数 147
EN

Stack Overflow用户

发布于 2018-05-08 09:53:42

使用withContext不是总是更好,而不是异步等待,因为它在功能上类似,但不会创建另一个协同机制。大型的numebrs协同器,虽然重量轻,但在要求应用程序时仍然是一个问题。 是否有案例异步等待比withContext更可取?

当您想要并发执行多个任务时,应该使用异步/等待,例如:

代码语言:javascript
复制
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() }
    //Or val results = deferredResults.awaitAll()
    println(results)
}

如果不需要同时运行多个任务,则可以使用withContext。

票数 34
EN

Stack Overflow用户

发布于 2019-09-30 12:08:10

当你有疑问的时候,记住这一点就像一条经验法则:

  1. 如果多个任务必须并行进行,并且最终结果取决于所有这些任务的完成,那么请使用async
  2. 要返回单个任务的结果,请使用withContext
票数 23
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50230466

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档