kotlin coroutines, what is the difference between coroutineScope and withContext
Asked Answered
B

1

50
withContext
suspend fun <T> withContext(
    context: CoroutineContext, 
    block: suspend CoroutineScope.() -> T
): T (source)
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result.
suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() -> R
): R (source)
Creates a CoroutineScope and calls the specified suspend block with this scope. The provided scope inherits its coroutineContext from the outer scope, but overrides the context’s Job.

the withContext takes CoroutineContext, and both seems to be complete after all its children are complete.

In what case the withContext or the coroutineScope should be preferred than the other?

for example:

suspend fun processAllPages() = withContext(Dispatchers.IO) { 
    // withContext waits for all children coroutines 
    launch { processPages(urls, collection) }
    launch { processPages(urls, collection2) }
    launch { processPages(urls, collection3) }
}

could also be

suspend fun processAllPages() = coroutineScope { 
    // coroutineScope waits for all children coroutines 
    launch { processPages(urls, collection) }
    launch { processPages(urls, collection2) }
    launch { processPages(urls, collection3) }
}

are the both processAllPages() doing the same?


update: see discuss at Why does withContext await for the completion of child coroutines

Burgonet answered 2/7, 2019 at 19:1 Comment(0)
C
70

Formally, coroutineScope is a special case of withContext where you pass in the current context, avoiding any context switching. Schematically speaking,

coroutineScope ≡ withContext(this.coroutineContext)

Since switching contexts is just one of several features of withContext, this is a legitimate use case. withContext waits for all the coroutines you start within the block to complete. If any of them fail, it will automatically cancel all the other coroutines and the whole block will throw an exception, but won't automatically cancel the coroutine you're calling it from.

Whenever you need these features without needing to switch contexts, you should always prefer coroutineScope because it signals your intent much more clearly.

coroutineScope is about the scoped lifecycle of several sub-coroutines. It's used to decompose a task into several concurrent subtasks. You can't change the context with it, so it inherits the Dispatcher from the current context. Typically each sub-coroutine will specify a different Dispatcher if needed.

withContext is not typically used to start sub-coroutines, but to temporarily switch the context for the current coroutine. It should complete as soon as its code block completes (as of version 1.3.2, this is actually still stated in its documentation). Its primary use case is offloading a long operation from the event loop thread (such as the main GUI thread) to a Dispatcher that uses its own thread pool. Another use case is defining a "critical section" within which the coroutine won't react to cancellation requests.

Caiaphas answered 3/7, 2019 at 9:8 Comment(9)
Thanks @Marko Topolnik! when you say for coroutineScope() "It's used to decompose a task into several concurrent subtasks.", does this also apply to withContext()? Besides that withContext() can specify context they both suspend until all children coroutines complete. Seems they both could be used in the places if context does not matter, isnt it? For withContext() it also mentions "This suspending function is cancellable.", does it implies it will be cancelled if the parent/call is cancelled? I guess in withContext() one sub-coroutines fails would not cancel its siblings.Burgonet
withContext is a cancellable suspending function in general, but if you use the NonCancellable context, it won't be cancellable. See here for more explanation and a motivating use case.Caiaphas
@MarkoTopolnik " It's used to decompose a task into several concurrent subtasks" could you elaborate on this. There's nothing concurrent about "coroutineScope" pl.kotl.in/q55LAN6mBCenacle
@FRR Your example doesn't attempt any concurrency. coroutineScope's purpose is to launch several concurrent sub-coroutines and await the completion of all of them, exactly what you need when doing parallel decomposition of a task. Here's an example that does that.Caiaphas
@MarkoTopolnik Thanks for the clarification, I understand what you mean now. So the TL;DR for this question would be: Yes, withContext and coroutineScope are semantically the same thing but you should be using one or the other depending on your intention.Cenacle
@FRR They are semantically the same only in the special case where you call withContext without actually switching contexts, which is clearly against its intended use.Caiaphas
Hum, withContext(this.coroutineContext) and coroutineScope are not the same thing. withContext will NOT wait for sub-coroutines you lunch inside it to complete, coroutineScope willPicky
@DanieleSegato Check out this question. In earlier (experimental?) versions it didn't wait, but since the introduction of structured concurrency, it does.Caiaphas
you are right, looking at the code I initially though they weren't doing the same thing but on a second look (more closely) they are both using a ScopedCoroutine in the case of same context. Sorry for the misleading comment.Picky

© 2022 - 2024 — McMap. All rights reserved.