16

Asynchronous Programming In Kotlin

 4 years ago
source link: https://www.tuicool.com/articles/7BJ3aqa
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Here’s the Halloween story to scare the average programmer with asynchronous programming. The async programming in Kotlin has become more and more popular lately. There’s only one official library kotlin-coroutines for doing asynchronous programming in kotlin. And coroutines are the main reason async programming becoming more popular in Kotlin.

Since kotlin is fully interoperable with Java and JVM ecosystem and we can easily use the Task<T> , Future<T> and Thread's for async programming. So, why bother to learn coroutines?

To answer this question, let’s take a step back and see why kotlin-coroutines API is the first priority when it comes to async programming in Kotlin.

Async Programming With Threads

We as programmers love the sequential way to execute a program. For example, if we’re working on an Android app and being a Android programmer we know that our application will be a single-threaded app. So, our app has this line of code that reads some kind of file (long file) from memory and returns the read content, and that means our app is doing nothing while processing the file. It is sitting waiting for a response in order to continue.

To standard fix this issue, of course, we can call another thread to read the content from file and our app is happy to do other work without being blocked.

Now we know, instead of being blocked by the long-running process we can simply call a new thread to do the task. We can also spin up multiple threads; each thread one thing at a time. Together these threads allow our app to do multiple things at a time.

If the next generation of programmers makes more intensive use of multithreading , then the next generation of computers will become nearly unusable. (Edward A. Lee, a professor at Berkeley.)

But beware multithreaded programs are more complicated and typically more error-prone, they include common trouble issues: race-conditions, dead-locks, resource starvation, cpu context-switching, etc…

  • QBBvQf7.png!web

Context Switching

Even though multi-threading is not an easy task to do with threads but if you still wanna go with here’s another example–let’s say you have an instance of your service running inside the server where many clients are connecting simultaneously, and some of the session are long-lived. That means that many threads may be active at the same time in the server. Too many threads can consume a large amount of memory and CPU time just for context-switching.

Basically, in an asynchronous world, context is switched only at defined switch points rather than in non-deterministic intervals.

  • 7jiuiqZ.png!web

Callbacks And Futures Style Async

Another thing we can use to perform async operation is by using callbacks and futures (aka promises) . A callback is a function and it means “Once this done execute this function”. The Java8 CompletableFuture has a whenComplete method that installs a callback to wait for completion without blocking, thus making it usable for asynchronous programming.

Let us consider a hypothtical method that performs operation asynchronously and takes a Callback<T> parameter to report its completion with either resulting value or an error.

fun login(username : String, password : String, callback : Callback<User>)

fun main() {
    login("ahsensaeed","12345", object : Callback<User>{
           override fun onSuccess(data : User) {
                 // handles success user
           }

           override fun onFailure(error : Throwable) {
                  // handle any IO error
           }
    })
}

The login method essetianlly executes and returns immedialtely allowing the app to do other things instead of sitting there waiting for response.

Multiple Callbacks

But what if after we get the user object in onSuccess method of Callback we need to make another asynchronous call for user home feed and user new inbox messages.

fun login(username: String, password: String, callback: Callback<User>)

fun homeFeed(id: String, callback: Callback<UserFeed>)

fun inboxMessages(id: String, callback: Callback<List<Message>>)

fun main() {
    login("ahsensaeed","12345", object : Callback<User>{
           override fun onSuccess(data : User) {
                 homeFeed(user.id, object : Callback<UserFeed>{
                        override fun onSuccess(feed : UserFeed) {
                             // handle user feed
                        }

                        override fun onFailure(error: Throwable) {
                                // handle error for user home feed 
                        }
                 })

                 inboxMessaged(user.id, object: Callback<List<Message>>{
                          override fun onSuccess(messages : List<Message>) {
                                 // handle user inbox messages
                          }

                          override fun onFailure(error : Throwable) {
                                  // handle error for user inbox messages
                          }
                 })
           }

           override fun onFailure(error : Throwable) {
                  // handle any IO error
           }
    })
}

At the above we’ve built up some big call stack and then calling a couple of IO functions. For performance, that operation uses the operating system’s underlying asynchronous API. we cannot wait for it to complete because it won’t. We have to return all the way back to our language’s event loop and give the OS some time to spin before it will be done.

This also becomes a problem because every callback acts like a thread, but there is no way to “gather” the tasks and return the aggregated results. We would have to hack around and save the result into some sort of global variables. Which is not good!

Note:The CompleteableFuture also doesn’t actually buy us anything, either. If you’ve used them, you know you’re still hand-creating giant piles of function literals. You’re just passing them to .whenComplete() or onError instead of to the asynchronous function itself.

Async Programming With Kotlin Coroutines

People in the kotlin community have realized that callbacks and futures are a pain for a long time, and come up with an excellent async kotlin-coroutines library. A coroutine is a new way of asynchronous, non-blocking code that can be thought of as light-weight threads. Coroutines can run in parallel or concurrently, wait for other coroutines and communicate with each other. As soon as you do any IO operation, it just parks the coroutines and resumes any other one that are not blocked by IO.

Without talking more about kotlin coroutines, first, let’s rewrite the multiple callbacks example with coroutines and see the difference.

suspend fun login(username: String, password: String) : User {
    // code to fetch user
}

suspend fun homeFeed(id: String) : UserFeed {
    // code to fetch the home feed
}

fun inboxMessages(id: String) : List<Message> {
   // code to fetch the inbox messages
}

suspend fun main() = coroutineScope {
     val user = login("ahsensaeed", "12345")
     val homeFeedJob = async {
         homeFeed(user.id)
     }
     val inboxMessageJob = async {
         inboxMessages(user.id)
     }
     // Both homeFeedJob and inboxMessageJob are running in parallel. 
     val response = UserResponse(homeFeedJob.await(), inboxMessageJob.await())
         // handle user response
}

You see the asynchronous code is most likely the same as synchronous code. In the coroutines world, they are suspending methods and promise free–they are not wrapped in any kind of Future<T> or Callable<T> , they are plain as simple as T's .

Ofcourse under the hood in JVM ecosystem the asynchrony is represented via callbacks and futures but we don’t need to make our hands dirty and apply Callable<T> to every method for async programming. Adding the suspend modifier is a small price to pay, but in return we get better insight and most of all the code is easy to read.

Let’s look at some of the benefit we get with coroutines in asynchronous programming:

  • The async functions return values just like the sync ones.
  • We don’t override another method for handling the error. We can simply use try/catch in the async code.
  • Coroutines enhance asynchronous programming by being light-weight and essentially faster than a thread as they are stackless. Well this means from a multiprocessing perspective coroutines don’t map on the native CPU thread, hence there’s no contextswitching on the processor.
  • Easy to call any UI related code inside the async code.
  • We are not looking for errors, because errors get passed up the stack correctly.
  • Cancellation is pretty much you just call cancel method on the outer scope of coroutine and all the inner coroutines will be canceled.

That’s all folks

Here lies the journey of asynchronous kotlin with coroutines. Like I mentioned above there are several options in kotlin to do async programmings like Callbacks , Threads , and Future . While the options are plentiful, the best one of them all is kotlin-coroutines . I encourage you to try out cor outines instead of threading for your next project.

I hope that this will enable you to write more performant apps with less boilerplate code and fewer bugs.

Thanks for being here and keep reading…


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK