Since Kotlin is officially favored for Android development, we try to get to know what coroutine is and how it benefits us writing asynchronous code but in a sequential manner. Coroutine also emits its standardization figure where we are no longer faced with some of old school async approaches, e.g., Thread, Handler, or AsyncTask (So long, partner ~)².

Introductory

Some may use coroutine in quite trivial needs like one-shot task, dispatching some heavy tasks to the background, and returning the result afterward, once each time it’s invoked. When complexity grows, however, there might be a need for how we can manage the ongoing coroutine task or the one to be executed in some specific situations.

Introducing the Executor of coroutine tasks (opinionated naming). One may interpret it as the one who will be managing the coroutine’s task. Executor is nothing but a bunch of abstracted basic patterns that we can make use of according to some specific needs without having the codebase getting cluttered by some of relative complex states.

To get more real picture, say whenever there is a newer API request, we want to cancel the ongoing coroutine which holds the previous request so that we don’t need to handle the incoming obsolete response (whatever the actual reason is). Therefore, the executor will abstract the work for us.

Below are defined executor types along with some of their use cases:

ConflatedExecutor

This type resembles the case I’ve introduced at glance previously. Imagine we’re developing a translator app and whenever the user is getting back to typing after stopping for some time millis, we would want to drop some ongoing previous API requests so that the app preserves the loading state instead of having last obsolete response shown up while the latest query has just been requested. This may cause ambiguity for the displayed translated result. Hence, we could assign this type to do the work since:

ConflatedExecutor cancels any ongoing coroutine task and executes the latest one.

Now let’s magnify of what each line does:

2nd
Since we want to be capable of canceling any ongoing active task, we load the task inside another child coroutine. Hence, it results Job.

We also wrap the Job with Atomic³ stuff since it benefits us for thread-safe: any updates made will be visible to other threads since they fetch from the memory.

5th
Create the coroutine scope where we want to launch a new coroutine inside.

6th
Create a new coroutine, in this case, using async to create one since we want to return a value from the delegated task. We also want to start the coroutine LAZY⁴-ly to ensure any previous active coroutine gets canceled first.

8th
This block gets invoked every time when an associated job completes or canceled. At this state, we should zero-out the atomic activeTask back once it’s no longer needed. Otherwise, memory leak could happen.

14th
Here we’re working with an aggressive tight loop to check if any active task/coroutine is ongoing. If none, we have the activeTask memorized the latest task.

15th
If there’s any, we want to cancel them completely⁶ and avoid a possible tight loop on a single thread by yield⁷-ing on 15th.

18th
After every ongoing task is canceled, now we can start the delayed coroutine and await for the promised result.

QueueExecutor

As the name implies, assume we’re doing a messaging app. Every time we tap the UI send button, it posts our message to an instance of what is called the “Message Broker” or similar ones to be processed in advance. In order guaranteeing the messages are posted in order (covering the case where the user presses the button in real quick), we want to assign the work for this type as:

QueueExecutor will just queue up every incoming coroutine task and execute them FIFO-ly.

Take notes for the special concurrency type w̶h̶i̶c̶h̶ ̶y̶o̶u̶ ̶p̶r̶o̶b̶a̶b̶l̶y̶ ̶m̶i̶g̶h̶t̶ ̶h̶a̶v̶e̶n̶’̶t̶ ̶s̶e̶e̶n̶: the Mutex.

2nd
If we are previously familiar with synchronized or ReentrantLock, Coroutine’s alternative would be Mutex⁸. Mutex resembles just like a key where an instance needs to acquire it to enter the block. This guarantees only one request runs at a time.

5th
Instead of defining manually

The extension withLock has already abstracted those clauses away. At this state, the coroutines are lining up and in suspended state.

6th
Now we have the key (we’re inside the block), time to execute the delegated task.

Note: Upon block completion by unlocking back the mutex, the key shall be passed to next inlined coroutine.

ConcurrentExecutor

Nothing more but our typical daily practice.

ConcurrentExecutor will just keep firing-up the coroutine tasks in parallel.

(Nothing fancy to be explained 😎)

The Aggregator

Regarding the above executor types, shouldn’t we need to reimport those related types every time we jump into similar use cases?

Well reflecting from personal practice, aggregating them into a central executor class will save the day. This central will then act as the dispatcher, assigning to which executor type based on a set of schemas.

Yet we’re much easier appending when there are other new executor types, clients can still keep their business side clean by only interacting with the single aggregator’s execute API. Just delegate the task and select which schema for how one would like the task is getting executed.

Sum Up,

Having this executor concept could offer codebase a strong foundation¹ on how we want to control the coroutine task execution in some advanced ways. Those above-mentioned 3 types are just the starter pack to begin with and the companion aggregator provides us with a scalable space whenever there are other existing variants we want to enrich with.

Alright, sharing is caring. Let’s share what other schemas you might have sparked with or if you have related questions, just leave the comments below. You can also view this sample project to give you more visual of each executor’s specialty. Thanks, hope this helps.

External links:

  1. https://github.com/ErickSumargo/Kirin
  2. https://developer.android.com/reference/android/os/AsyncTask
  3. https://www.baeldung.com/java-atomic-variables
  4. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y.html
  5. http://man.hubwiz.com/docset/Kotlin.docset/Contents/Resources/Documents/api/latest/jvm/stdlib/kotlin.native.concurrent/-atomic-reference/index.html
  6. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html
  7. https://www.lukaslechner.com/how-to-run-an-expensive-calculation-with-kotlin-coroutines-on-the-android-main-thread-without-freezing-the-ui/
  8. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/

--

--