Simple Coroutine in non-Android land best practices


#1

I want to make sure I’m doing this the right way. I’ve read the latest blog and tutorial, but think I am missing the bigger picture. I have questions Q1-Q4, all of which I hope to do with Kotlin 1.3, best practices, etc.

class RemoteControl : AutoCloseable, CoroutineScope {
    private val job = Job() // Q1: One job per class?  I think this is what makes the hierarchy, true?  This seems odd to repeat these two lines in every class...
    override val coroutineContext: CoroutineContext get() = job + Dispatchers.Default // Q2: "Default" should work in most cases, right? (a bit of parallel, better than blank which is main thread and won't do in parallel)

    private val config = async(coroutineContext) { // Q3: is "coroutineContext" still needed here?  Or is it automatic with the previous configs?
        JsonParser().parse(URL("https://server/config.json").readText()).asJsonObject.getAsJsonObject("wbb").getAsJsonObject("board01")
    }

    private fun doStuff() {
        // do some stuff
    }

    override fun close() {
        LOG.debug { "RC close()" }
        // Close other nested stuff
        job.cancel() // Q4: Is this needed, or does it happen automatically?
    }
}

#2

I think the idea of a Coroutine Scope is that you “enter” a section of your program that creates coroutines, and when you “leave” that section you can cancel all the coroutines you created in it because you no longer need whatever those coroutines were doing. The easiest example to understand is probably the Android screen, which is represented by an Activity or ViewModel. When the user leaves the screen you want to cancel any coroutines started by the screen, so you make the Activity or ViewModel a CoroutineContext.

I don’t have a good non-UI/Android example of a scope (because I’m an Android programmer and we currently only use coroutines in the UI).

To answer your questions,
A1: You want all the coroutines created in the scope to use the same Job so you can cancel them all at once. You want one Job per scope so you can cancel one scope without affecting the other scopes in your program.

A2: Dispatchers.Default is the, well, default dispatcher (not the Main dispatcher as your question assumes). So technically you could leave it out, but I’d include it just to make it obvious.

A3: No, async() is an extension function on CoroutineScope, and gets the context from coroutineContext. You would only specify a context if you wanted to override coroutineContext's dispatcher.

A4: It does not happen automatically. You need the cancel the Job when the program leaves the context.


#3

It took me a while to understand the model – the concept of CoRoutine Scope is as ebrowne72 says. I find it useful to think of it in terms of “DSL Scope” – the set of language features that allows “DSL Like” syntax – deeply uses the concept of ‘lexical scope’ in ways other languages I am used do not and can not. One pillar of this the concept of ‘receivers’ – having multiple and nested ‘receivers’ ‘in scope’ which allow for ‘scoped context’. These are NOT the same as class level or object instance scope, although both end up as ‘this’. When you understand how receivers work and the kotlin idioms that leverage them for ‘DSL Like’ syntax THen the implementation of CoRoutine Scopes makes mores sense. Sope is derived from the ‘Caller’ perspective – I quoted ‘lexical scope’ ( a Term I abuse for this) because it follows, IMHO, what looks like ‘texutal’ proximity not object heirchy, inheritance etc. It doesn’t work when you call into functions then lose the caller’s ‘lexical scope’.

When you understand how for example the Stream extensions (that serve the same purpose as Java’s resoruce try/catch blocks) like:

   "file.txt".apply {
       File(this).writer.use {    // "this" is "file.txt" 
           // 'it' is Writer
            it.println( length )  // 'length' is this.length == String.length 
           func()  // 'it' is NOT passed to fun , nor is "file.txt"
       }
}

Then the design pattern for CoRoutine Scopes make much more sense.
Like the above snippet – scopes apply following the same rules as non-prefixed variable and method names – the ‘scopes’ are only ‘in scope’ when the code can ‘see’ them …
( and yes that’s very imprecise and an abuse of metaphors – but the precise terms are hard to understand intuitively at first )
If you are used to languages like Java, C++, C# etc these are foreign and non-obvious concepts.