Skip to content

How asynchronous programming works

Devrath edited this page Oct 8, 2021 · 8 revisions

User Interface

  • User Interface is something an end-user uses to interact with the system.
  • Using this mechanism the end-user interacts with the mobile and communicates with it to perform certain tasks.
  • Once the tasks get completed, we show the result to the end-user.
  • If the task that is being performed is taken a lot of time, we need to show feedback to the end-user that a task is in progress, otherwise the user thinks that the application is not responding, or worse it doesn't work at all.

Uploading Data to server

  • Once good example is that, say we are uploading a photo to the server and this photo is very large so naturally it takes time to upload the photo.
  • Since it is taking a lot of time we need to show some feedback to users that an uploading task is in progress.
  • We can do this by showing a loader to the end-user and once the uploading is done, we close the loader.
fun uploadImageToServer(image: Image) {
  showLoader() // --- > This triggers spinning of loader and starts displaying loader
  service.upload(image) // --- > Here the data is sent as bytes to server 
  hideLoader() // --- > This triggers spinning of loader and stops displaying loader
}
  • Here in this simple function uploadImageToServer lot of things happen
  • showLoader() calls the loader to display, Now let's assume we are calling the android loader, this is not just a call it's an android functionality built that shows an animation. But so it's doing some operation.
  • If service.upload need to wait until showLoader() gets completed the service.upload becomes a blocking call.
  • Similarly hideLoader() functionality to stop the above loader
  • So here the concept of multi-threading comes to the picture, If we have many threads that perform tasks parallel, We can do more things at once.
  • In the above example, We show the loader in UI-thread and the service call, we perform in a background thread doing so we need not need to wait for individually things to complete to continue to next instead, We can do things parallel.

How main thread is different than worker threads

  • We use the main thread to manage the UI
  • Each application has only one thread called main thread to avoid the deadlock.
  • We use the many threads to achieve what we called concurrency with the help of multi-threading.
  • Understanding how threads communicate is the key to knowing concurrency.
  • All threads should be able to communicate between them seamlessly without causing problems.
  • Understanding how threads communicate is key in knowing how concurrency happens between the threads.
  • Communicating between the threads is a challenge, it is the key to the challenge of achieving a well-written concurrent code.
  • There needs to be proper synchronization.
    • Say you are displaying the loader, so we display multiple frames of images one after another -> Now If before one image is displayed another image replaces it, There is a loss in the frame. So we need a data structure that manages a thread-safe condition meaning the data structure should work correctly even though multiple threads access it.
    • Accessing one data from multiple places maintaining concurrency and performance is not easy.

Good data structure for thread safety

  • We can use queue as a good example for thread safety.
  • Threads communicate using the queue as producers and consumers, The producer is the one who puts the data into the queue and the consumer is the one who takes the data from the queue.
  • Data structure we call it as first in first out. Threads put the information to queue as messages thus encapsulating it.
  • Using this queue system. The consumer can wait until the message becomes available and if the thread is blocking the queue the consumer can block the queue and just try on after some time.
  • A good example we can take is, say there is a queue of customers in the food delivery shop KFC, Now if the food is available the consumer thread that is the person that servers the food at the counter give the food, now if food is not available the consumer will make the customers wait in the queue and block the queue so customers wait, later once the food is prepared, he unblocks the queue.

Callback Hell

  • One challenge we face in asynchronous programming is callback nesting.
    • Consider a scenario, We upload an image to server : show the spinner --then--> load the image --then--> resize the image --then--> upload the resized image --then--> hide the spinner
    • Each step has success and failure blocks, and they have to be nested inside another, we can observe a lot of nested levels here itself.
    • Drawback of using the nested way is passing data from one to another leading to nested levels and hard to maintain code

Reactive solution for callback hell

  • Using rx or the reactive extensions, we can have asynchronous functions wrapped in the stream of events.
  • Rx provides useful constructs called operators and uses the observer pattern
  • You can subscribe to a stream of events map, filter, reduce, combine them using the chain of operators with a single lambda event.

Callback Sample

fun uploadImage(imagePath: String) {
  showLoadingSpinner()
  loadImage(imagePath) { image ->
    resizeImage(image) { resizedImage ->
      uploadImage(resizedImage) {
        hideLoadingSpinner()
      }
    }
  }
}

Callback Sample transformed to reactive sample

fun uploadImage(imagePath: String) {
  loadImage(imagePath)
    .doOnSubscribe(::showLoadingSpinner)
    .flatMap(::resizeImage)
    .flatMapCompletable(::uploadImage)
    .subscribe(::hideLoadingSpinner, ::handleError)
}
  • In the modified rx sample, we can observe that a stream of data is being modified by a bunch of rx operators
  • Data is passed from loadImage() ---> to another function creating new stream ----> Then the modified stream is passed to another function called resizeImage() ---> Then now yet again the modified stream is passed to uploadimage() where the image is uploaded to server ---> These stream is not executed until someone subscribes to them ---> When someone subscribes to them doOnSubscribe() is executed.
  • One more advantage here is, If there is an exception in the stream at any point here, It is not thrown straight away instead it's propagated down the stream and handled at one place.

Problems in Rx based approach

  • Even though it solves these problems associated with the traditional approach, the rx brings a lot more complexity to the table things like observer design pattern, knowing about streams,should we need to consider clicks also as streams or not,it can be simpler and then why to make it so complex,learning curve is high here.

A simpler way to deal with synchronicity is by using coroutines

Clone this wiki locally