anyone having a good understanding of kotlin coroutines? the weird extension lambdas, coroutine scopes, context , dispatcher and suspend are not making much sense to me. i can post a longer query, but first i just need to understand the relationship between a scope, context and dispatcher?

  • 2
    I learned them with an Udemy course that explains it well, because after reading about them in blogs/youtube/documentation/etc I didn't understand anything.
  • 6
    I have this giant book about it (2022 Version). Want to have it? I'm not planning to read it anytime soon.

    Edit: not sure if your target even is Android. But likely i guess.
  • 2
    I can try by drawing analogy to JS since I assume you have used react native before, although it's simpler to compare with C# async/await.

    Basically, a scope is a builder, and a lifetime manager. It is really an interface, (think Promise) that comes with several premade instances that you build through, say, "launch()", "async()", "runBlocking()", etc.

    These control the lifetime of your coroutine, meaning they get to decide when to shut off the passed coroutine and its children.

    In JS, promises aren't cancelable and as such, there are no scopes, but in Android kotlin, you can, for example, build a coroutine in the, say, activity scope, meaning that whenever the activity is destroyed, any coroutine built with its scope (and its children) will immediately stop executing (at the next suspension point really but nevermind this), therefore preventing leaks or other undesirable things like NPEs.
  • 2
    So you can think of scopes as Promise instances that can take an AbortSignal in their constructor to be cancelled, which is attached to the lifetime of some other construct.

    Contexts, on the other hand, are simply data holders. They are essentially a map that, most importantly, stores the current job and dispatcher of the coroutine. In other languages, this is often referred as the "sync frame" of the coroutine, which allows for stack unwinding, traces and such, as well as the interaction with sync code.

    Think of it as a function frame that gets pushed into the stack like any other function, but instead of holding the register state, local parameters and variables, etc, it contains a pointer to a heap-allocated frame that actually holds that information, as well as the execution state of the coroutine (remember coroutines are state machines really).
  • 2
    Which brings us to the last piece, Dispatchers. Dispatchers define *where* should coroutines run.

    Like scopes, there are several prebuilt ones that you access statically, like Dispatchers.Main and Dispatchers.IO.

    The first makes the coroutine execute in the main thread, while the second makes it run in the thread pool. You can actually switch contexts within a coroutine with the withContext() construct, which is useful for, say, fetching some data from the net (which you can't do on the main thread without getting a NetworkInMainThreadException) and then updating some View (which you can only do from the main thread) in the same coroutine.

    Of course, the dispatcher associated with a coroutine must be stored somewhere, and that is in the coroutine context.

    In JS, dispatchers would be the equivalent of just running new Promise() in the main thread or creating a web worker. In C#, they are analogue to SynchronizationContexts.
  • 4
    Bear with me that not everything I've said here is technically exact, as different implementations often can't be compared (just saying before anyone comes nitpicking).

    But as TL/DR, think of scopes as "who runs me", dispatchers as "where do I run" and contexts as "what should I run now?".
Add Comment