44
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

lannyf
  • 9,865
  • 12
  • 70
  • 152

1 Answers1

63

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • 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. – lannyf Jul 03 '19 at 13:12
  • 1
    `withContext` is a cancellable suspending function in general, but if you use the `NonCancellable` context, it won't be cancellable. See [here](https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#run-non-cancellable-block) for more explanation and a motivating use case. – Marko Topolnik Jul 04 '19 at 07:14
  • @MarkoTopolnik " It's used to decompose a task into several concurrent subtasks" could you elaborate on this. There's nothing concurrent about "coroutineScope" https://pl.kotl.in/q55LAN6mB – frankelot Oct 01 '19 at 12:13
  • @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](https://pl.kotl.in/qYle_nuK9) an example that does that. – Marko Topolnik Oct 01 '19 at 13:19
  • @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. – frankelot Oct 02 '19 at 14:09
  • 1
    @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. – Marko Topolnik Oct 02 '19 at 15:55
  • 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` will – Daniele Segato Sep 02 '20 at 09:53
  • 3
    @DanieleSegato Check out [this question](https://stackoverflow.com/questions/56904057/why-does-withcontext-await-for-the-completion-of-child-coroutines). In earlier (experimental?) versions it didn't wait, but since the introduction of structured concurrency, it does. – Marko Topolnik Sep 02 '20 at 11:00
  • 1
    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. – Daniele Segato Sep 03 '20 at 08:49