```class MyWorker { private val scope = Corou...
# coroutines
u
Copy code
class MyWorker {

    private val scope = CoroutineScope(SupervisorJob() + <http://Dispatchers.IO|Dispatchers.IO>)

    suspend fun doStuff() {
        val deferred = scope.async {
            ...some async stuff
        }
        deferred.await()
    }
}
This tries to not be cancelled by running the operation on it's own scope - yet it awaits the deferred Does this code break structured concurrency?
s
Not exactly. But if you're not managing the lifecycle of your new scope, then yes. Why do you want to do it this way?
u
Well, because it seems to work as expected, and I want to know the exact reason why is it bad
the idea is to let
doStuff
finish (not get cancelled), even if the calling scope is gone (viewmodel)
s
If you just want to avoid cancellation, you can use
withContext(NonCancellable)
u
that has always felt like a hack, and is meant as for stuff that really needs to run because ..reasons, like resource cleanup not high level business logic
anyways, in a actual app, I'd want for the
doStuff
to get cancelled when it's parent dies, like user ,ie. logout so it should not blankly finish no matter what it just should not be scoped to the view model, from which it is launched
Copy code
class MyWorker {

    private val scope = CoroutineScope(SupervisorJob() + <http://Dispatchers.IO|Dispatchers.IO>)

    fun doStuff() {
        scope.launch {
            ...some async stuff
        }
    }
}
does this still break structured concurrency?
s
Okay, I think I understand. If you have some code that cancels your new
scope
, I think either approach you showed is fine. It sounds like that's the case, since you said that
doStuff
should still be cancelled if its parent dies. It's okay for coroutines in one scope to launch and await coroutines in another, provided you take care with cancellation exceptions.
Delegating coroutines to a separate custom scope that has proper cancellation/lifecycle and/or error handling = good. Creating a new scope without any cancellation just so you can launch coroutines without waiting for them = breaking structured concurrency.
u
It's okay for coroutines in one scope to launch and await coroutines in another
but doesn't this exactly break structured concurrency? i.e. the caller's scope doesnt control it's launched coroutines
Creating a new scope without any cancellation just so you can launch coroutines without waiting for them = breaking structured concurrency.
how so? just because the
private val scope
is not explicitly cancelled? (I ommited that for brevity)
s
In a purist sense, yes. For structured concurrency to work perfectly, every scope would have to be descended directly from a single suspending main function. In practice, most apps have one or more custom scopes that have an independently managed lifecycle. Technically the viewModelScope itself violates structured concurrency. Or you could say it creates a new structured concurrency root, but I just made that terminology up šŸ˜„
u
maybe say what I understand under struct. conc? I though it means "coroutines have parent - child relation ship; children cannot outlive parent, cancelling parent cancels children"
šŸ‘ 1
so by that logic
Copy code
class MyWorker {

    private val scope = CoroutineScope(SupervisorJob() + <http://Dispatchers.IO|Dispatchers.IO>)

    fun doStuff() {
        scope.launch {
            ...some async stuff
        }
    }
}
should not break structured concurrency, as it does not apply .. it's a blocking function, doesnt participate ..no?
s
I would say that ā€œdoesn’t participateā€ is the same thing as ā€œbreaks structured concurrencyā€
āž• 1
u
why, there is no structuring going in, if they're unrelated .. I'd say
so there's no expectation for it to behave such way
s
I think we’re agreed on the basics, we’re just using different ways to describe it. I’d say that launching background tasks (i.e. coroutines) without waiting for them is breaking structured concurrency, by definition. But I agree that if you don’t have the suspend modifier on your function, you have made no promises to wait for anything, and so breaking structured concurrency isn’t necessarily an issue. Most real production apps aren’t going to have a single suspending main function that can wait for everything. So a few strategically placed structured concurrency violations are normal.
u
well I think it usually 2 thing in a average app, scoping the action to screen, scoping the action to login (say you want a timer that is per screen; and another timer that keeps ticking whole time while user is logged in)
so .. what would be your api for the 2nd timer?
s
It depends on the framework. If we were using pure coroutines, each screen scope could be a child of the user-login-session scope. In practice, the framework might now know anything about coroutines, so you have to improvise with custom scopes.
u
can you sketch it over the sample I sent?
s
It’s hard to be specific without picking a specific framework or type of app. Are you using Android?
u
no frameworks, just kotlin coroutines yes android
s
In that case I’d worry less about structured concurrency and more about Android’s own task safety. You don’t want to be leaking tasks outside of their Activity/ViewModel, whether you’re using coroutines or not.
I think you should probably use a WorkManager with a CoroutineWorker for your use case, but I’m not an Android expert
u
why bring WorkManager into this? WorkManager is meant to enable background execution.. okay maybe I didn't specify, I'm only interested in while app is in foreground, pausing on going to bg is fine
s
Like I said, I’m not an Android expert. You said that you want the task to continue after the viewmodel is closed, so I thought a WorkManager might be appropriate for that, but I’m really just guessing. What I do know is that the coroutine scope that belongs to the viewmodel is cancelled when the viewmodel is cleared. Structured concurrency can only help you with tasks that live inside the scope. Once you’re outside the viewmodel and its scope, you need a different way to make sure that you’re not leaking tasks, and I imagine there will be some other Android API for that.
u
there's no apis for that
and it doesn't really matter, that a viewmodel has a scope and it dies with it is abitrary (a good choice, but arbitrary)