I want to do the below with coroutines but not sure what's the right way to do this and if there is another way to do this. What I want is this:
I want to create a lib that uses its own coroutine scope for requests/responses while allowing cancelling this coroutine scope when cancel method is called without cancelling the outer scope from which method of the lib is called.
For example, when a client code that has their own coroutine scope calls my lib method, I want to switch from their scope to my coroutine scope. And then, when some other coroutine/code calls cancel, I want to cancel my coroutine scope(and all it's children coroutines) without cancelling the outer scope that called the method.
Example of code:
private val myCoroutineScope = CoroutineScope(...)
suspend fun myLibMethod(){
//execute the this method in the myCoroutineScope
}
suspend fun cancel(){
myCoroutineScope.cancel()
}
For now I came up with this solution which I am not sure if it's the right one and if it's reliable:
suspend fun myLibMethod(){
withContext(myCoroutineScope.coroutineContext){
....
}
}
I wrote and tried this code which does not print Done and cancels I think the outer scope as well because of the exception:
runBlocking {
val coroutineScope = CoroutineScope(Dispatchers.Default)
println("Launching coroutine that will stop the coroutine scope after 2 seconds.")
launch(Dispatchers.Default) {
delay(2_000)
coroutineScope.cancel()
}
println("Changing coroutine context to the coroutine scope' context")
withContext(coroutineScope.coroutineContext){
delay(10_000)
}
println("Done!")
}
Actually, the above works - it does not cancel the outer scope if I catch the exception(isActive returns true). I am not still sure though if that's the best solution. I also see an example with Deferred that is cancelled when the lib's scope is cancelled(which looks better). Maybe someone has faced a similar problem and knows a better solution
Related
I'm learning the Coroutines of kotlin.
The Image A can get the correct result.
I think I have used the code runBlocking, and main function will keep runing until it get final result, but Image B failed, why?
Image A
Image B
When you use runBlocking, your code between the { } will run inside a CoroutineScope. If you run a child coroutine inside with launch, it will behave as you expected, because the parent coroutine has to wait for all its children before completing:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello, ")
}
However, when you use GlobalScope.launch to launch a new coroutine, it will not be tied to any parent, and therefore it won't be waited for, unless you add job.join().
See this:
Children of a coroutine
When a coroutine is launched in the CoroutineScope of another
coroutine, it inherits its context via CoroutineScope.coroutineContext
and the Job of the new coroutine becomes a child of the parent
coroutine's job. When the parent coroutine is cancelled, all its
children are recursively cancelled, too.
However, when GlobalScope is used to launch a coroutine, there is no
parent for the job of the new coroutine. It is therefore not tied to
the scope it was launched from and operates independently.
runBlocking will not return until all coroutines in its own scope have finished. Since your job is running on the GlobalScope instead it does not wait until it is finished. job.join() forces it to wait until the job (even from another scope) is done.
If you remove GlobalScope. it will launch on its internal scope and will work as you expect it should.
From the kotlin documentation about coroutines basics:
Structured concurrency
There is still something to be desired for practical usage of
coroutines. When we use GlobalScope.launch, we create a top-level
coroutine. Even though it is light-weight, it still consumes some
memory resources while it runs. If we forget to keep a reference to
the newly launched coroutine it still runs. What if the code in the
coroutine hangs (for example, we erroneously delay for too long), what
if we launched too many coroutines and ran out of memory? Having to
manually keep references to all the launched coroutines and join them
is error-prone.
There is a better solution. We can use structured concurrency in our
code. Instead of launching coroutines in the GlobalScope, just like we
usually do with threads (threads are always global), we can launch
coroutines in the specific scope of the operation we are performing.
In our example, we have main function that is turned into a coroutine
using runBlocking coroutine builder. Every coroutine builder,
including runBlocking, adds an instance of CoroutineScope to the scope
of its code block. We can launch coroutines in this scope without
having to join them explicitly, because an outer coroutine
(runBlocking in our example) does not complete until all the
coroutines launched in its scope complete. Thus, we can make our
example simpler:
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // launch a new coroutine in the scope of runBlocking
delay(1000L)
println("World!")
}
println("Hello,")
}
Adding to the point made by #huytc
To better understand the parent-child hierarchy you can use the job.children.contains like below
val customScope = CoroutineScope(Dispatchers.Default)
fun main() = runBlocking {
val job = customScope.launch {
println("processing")
delay(1000)
println("done processing")
}
println("Is custom scope child of run-blocking: ${this.coroutineContext.job.children.contains(job)}")
Output:
processing
Is custom scope child of run-blocking: false
I am doing
private val uiScope = CoroutineScope(Dispatchers.Main)
to create a coroutinescope and using that to launch the coroutines in my fragment.
uiScope.launch {
withContext(Dispatchers.Default) {
....
}
....
}
I do a cancel on uiScope when the fragment is detached from window. While creating the uiScope should i be passing a job() as well?
The documentation of fun CoroutineScope is clear on this:
If the given context does not contain a Job element, then a default Job() is created. This way, cancellation or failure or any child coroutine in this scope cancels all the other children, just like inside coroutineScope block.
Using a job that propagates the failure of its children is not the best choice for the top-level scope. You should instead use the MainScope factory function. It takes no arguments and constructs exactly the scope you need. From the docs:
The resulting scope has SupervisorJob and Dispatchers.Main context elements.
I'm writing an app using coroutines (code below is greatly simplified). Recently I've watched Coroutines in Practice talk and got a little confused. Turns out I don't know when to use a CoroutineScope's extension function and when to use a suspending function.
I have a mediator (Presenter/ViewModel/Controller/etc) that implements CoroutineScope:
class UiMediator : CoroutineScope {
private val lifecycleJob: Job = Job()
override val coroutineContext = lifecycleJob + CoroutineDispatchersProvider.MAIN
// cancel parent Job somewhere
fun getChannel() {
launch {
val channel = useCase.execute()
view.show(channel)
}
}
}
Business logic (Interactor/UseCase):
class UseCase {
suspend fun execute(): RssChannel = repository.getRssChannel()
}
And a repository:
class Repository {
suspend fun getRssChannel(): RssChannel {
// `getAllChannels` is a suspending fun that uses `withContext(IO)`
val channels = localStore.getAllChannels()
if (channels.isNotEmpty()) {
return channels[0]
}
// `fetchChannel` is a suspending fun that uses `suspendCancellableCoroutine`
// `saveChannel` is a suspending fun that uses `withContext(IO)`
return remoteStore.fetchChannel()
.also { localStore.saveChannel(it) }
}
}
So I have a few questions:
Should I declare Repository#getRssChannel as a CoroutineScope's extension function (because
it spawns new suspending functions: getAllChannels,
fetchChannel, saveChannel)? How can I use it in the UseCase then?
Should I just wrap a Repository#getRssChannel into a
coroutineScope function in order to make all spawned suspending
functions to be children of the latter?
Or maybe it's already fine and I should change nothing. When to
declare a function as a CoroutineScope's extension then?
A suspending function should return once it has completed its task, it executes something, possibly taking some time while not blocking the UI, and when it's done it returns.
A CoroutineScope extension function is for a fire-and-forget scenario, you call it, it spawns a coroutine and returns immediately, while the task continues to execute.
Answer to question 1:
No, you should not declare Repository#getRssChannel as an extension function of CoroutineScope, because you only invoke suspend functions but not start (launch/ async) new jobs. As #Francesc explained extension function of CoroutineScope should only start new jobs, but cannot return immediatly result and should not be declared as suspend by itself.
Answer to question 2:
No, you should not wrap Repository#getRssChannel into a CoroutineScope. Wrapping makes only sense if you start (launch/ async) new coroutines in this method. The new jobs would be children of the current job and the outer method will only return after all parallel jobs are finished. In your case you have sequential invocations of other suspending coroutines and there is no need of a new scope.
Answer to question 3:
Yes, you can stay with your code. If you would need the functionality of UiMediator#getChannel more then once, then this method would be a candidate of an extension function for CoroutineScope.
I need test() to return a player from my db. I know I can use a callback but how can I make this work with async await?
fun test(): Player {
launch(UI) {
val player = async(CommonPool) { MainActivity.database?.playerDao()!!.loadPlayer() }.await()
return player
}
}
Currently the error is return is not allowed here
In JavaScript for example I would make test async then await it's result from where it's called.
It is impossible to run a coroutine on a raw thread. At the very least you must turn an existing thread into one that spins a top-level event loop. You achieve this with a runBlocking call on the very top of the thread's call stack (i.e., inside its run() method).
On a GUI thread or any other kind of thread that runs an event loop, you need a matching Dispatcher that submits coroutines to this event loop. Kotlin already provides dispatchers for Swing, JavaFX, Android etc. In these cases you need to launch a coroutine from some existing GUI event handler, like this:
myScope.launch {
val player = test()
... use the player ...
}
myScope must be an object that implements CoroutineScope with something like this:
override val coroutineContext = Dispatchers.Main + SupervisorJob()
This will give you a way to cleanly cancel all the coroutines running within the same scope, by calling
coroutineContext[Job]!!.cancel()
My example uses the Main dispatcher, which resolves to the GUI thread when you import the Kotlin coroutines library matching your UI framework.
The test() function must become a suspend fun that temporarily switches the dispatcher to a thread pool for blocking operations. Here's how a basic example could look:
suspend fun test() = withContext(Dispatchers.IO) {
MainActivity.database?.playerDao()!!.loadPlayer()
}
Finally, note I don't mention async at all in this answer. Kotlin's async has a very specific purpose, it is not a general facility like in other languages. Its purpose is strictly parallel decomposition, where you decompose a single task into several concurrent subtasks.
I am at loss with the following problem.
I have the following code:
val parentJob: Job = Job()
launch(parent = parentJob) {
while (true)
{
if (!parentJob.isCompleted)
{
// I want to control suspension here
println("Resumed")
}
}
}
I would like to be able to control, somehow akin to a semaphore, when should the coroutine suspend and when to resume exactly in the commented part of the snippet
I know there's suspendCancellableCoroutine but I am unsure how to use it or if it is appropriate here
How can this be achieved or are there any tutorials about this?
It would be more helpful to think about coroutines in terms of callbacks and continuations, not threads and semaphores.
Internally, when the coroutine is suspended, the entire chain of suspend fun calls returns with a special internal object, COROUTINE_SUSPENDED. So the whole execution of a coroutine is just a function call during which a callback object is being built up. This callback is the continuation and when you call it, execution resumes from the place which returned the special COROUTINE_SUSPENDED object.
Kotlin runs the block you pass to suspendCancellableCoroutine with the continuation object as the parameter. So you should save this object and make it available to the code outside the coroutine.
Here's some code that may help your understanding. Note there's no need to create a separate parent job if you want to cancel the coroutine. You can just cancel the Job instance that launch returns and the coroutine will not resume after suspension.
import kotlin.coroutines.*
import kotlinx.coroutines.*
var continuation: Continuation<String>? = null
fun main(args: Array<String>) {
val job = GlobalScope.launch(Dispatchers.Unconfined) {
while (true) {
println(suspendHere())
}
}
continuation!!.resume("Resumed first time")
continuation!!.resume("Resumed second time")
job.cancel()
continuation!!.resume("This shouldn't print")
}
suspend fun suspendHere() = suspendCancellableCoroutine<String> {
continuation = it
}
If you still need an explicit check (because there aren't enough suspension points on the execution path), you can just use the isActive property which is available directly to the block:
while (isActive) ...