Kotlin : withContext () 대 Async-await
나는 kotlin 문서 이며 올바르게 이해하면 두 개의 kotlin 함수가 다음과 같이 작동합니다.
withContext(context)
: 현재 코 루틴의 컨텍스트를 전환합니다. 주어진 블록이 실행되면 코 루틴이 이전 컨텍스트로 다시 전환됩니다.async(context)
: 주어진 컨텍스트에서 새 코 루틴을 시작.await()
하고 반환 된Deferred
작업을 호출하면 호출 된 코 루틴을 일시 중단하고 생성 된 코 루틴 내부에서 실행중인 블록이 반환 될 때 다시 시작합니다.
이제 다음 두 가지 버전이 있습니다 code
.
버전 1 :
launch(){
block1()
val returned = async(context){
block2()
}.await()
block3()
}
버전 2 :
launch(){
block1()
val returned = withContext(context){
block2()
}
block3()
}
- 두 버전 모두 block1 (), block3 ()은 기본 컨텍스트 (commonpool?)에서 실행됩니다. 여기서 as blocks ()은 주어진 컨텍스트에서 실행됩니다.
- 전체 실행은 block1 ()-> block2 ()-> block3 () 순서와 동기화됩니다.
- 내가 보는 유일한 차이점은 version1이 다른 코 루틴을 생성한다는 것입니다. 여기서 version2는 컨텍스트를 전환하는 동안 하나의 코 루틴 만 실행합니다.
내 질문은 다음과 같습니다.
기능적으로 비슷 하기
withContext
보다는 항상 사용하는 것이 더async-await
좋지만 다른 코 루틴을 만들지는 않습니다. 많은 수의 코 루틴은 가볍지 만 까다로운 애플리케이션에서는 여전히 문제가 될 수 있습니다.async-await
더 바람직한 경우 가withContext
있습니까?
업데이트 : Kotlin 1.2.50 에는 이제 async(ctx) { }.await() to withContext(ctx) { }
.
많은 수의 코 루틴은 가볍지 만 까다로운 응용 프로그램에서 여전히 문제가 될 수 있습니다.
실제 비용을 정량화하여 "너무 많은 코 루틴"이 문제가된다는이 신화를 없애고 싶습니다.
먼저 코 루틴 이 연결된 코 루틴 컨텍스트 에서 코 루틴 자체를 분리해야합니다 . 이것은 최소한의 오버 헤드로 코 루틴을 만드는 방법입니다.
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
약 2 배 정도 걸리지 withContext
만 여전히 1 마이크로 초입니다. 앱에서 "문제"가 되려면 그 외에는 거의 아무것도하지 않고 타이트한 루프로 실행해야합니다.
사용 measureMemory()
하여 다음과 같은 호출 당 메모리 비용을 찾았습니다.
Just launch: 88 bytes
withContext(): 512 bytes
async-await: 652 bytes
The cost of async-await
is exactly 140 bytes higher than withContext
, the number we got as the memory weight of one coroutine. This is just a fraction of the complete cost of setting up the CommonPool
context.
If performance/memory impact was the only criterion to decide between withContext
and async-await
, the conclusion would have to be that there's no relevant difference between them in 99% of real use cases.
The real reason is that withContext()
a simpler and more direct API, especially in terms of exception handling:
- An exception that isn't handled within
async { ... }
causes its parent job to get cancelled. This happens regardless of how you handle exceptions from the matchingawait()
. If you haven't prepared acoroutineScope
for it, it may bring down your entire application. - An exception not handled within
withContext { ... }
simply gets thrown by thewithContext
call, you handle it just like any other.
withContext
also happens to be optimized, leveraging the fact that you're suspending the parent coroutine and awaiting on the child, but that's just an added bonus.
async-await
should be reserved for those cases where you actually want concurrency, so that you launch several coroutines in the background and only then await on them. In short:
async-await-async-await
— same aswithContext-withContext
async-async-await-await
— that's the way to use it.
Isn't it always better to use withContext rather than asynch-await as it is funcationally similar, but doesn't create another coroutine. Large numebrs coroutines, though lightweight could still be a problem in demanding applications
Is there a case asynch-await is more preferable to withContext
You should use async/await when you want to execute multiple tasks concurrently, for example:
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)
}
If you don't need to run multiple tasks concurrently you can use withContext.
When in doubt, remember this like a rule of thumb:
If multiple tasks have to happen in parallel and final result depends on completion of all of them, then use
async
.For returning the result of a single task, use
withContext
.
참고URL : https://stackoverflow.com/questions/50230466/kotlin-withcontext-vs-async-await
'Development Tip' 카테고리의 다른 글
누구든지 Angular2에서 양식 유효성 검사기를 트리거하는 방법을 알고 있습니까? (0) | 2020.11.24 |
---|---|
NodeJs : TypeError : require (…)는 함수가 아닙니다. (0) | 2020.11.24 |
WCF가 List 대신 myObject []를 반환하는 이유 (0) | 2020.11.24 |
hashCode ()가 재정의되지 않은 경우 객체의 해시 코드는 무엇입니까? (0) | 2020.11.24 |
C ++에 대한 까다로운 인터뷰 주제 (0) | 2020.11.24 |