If you are familiar with the concept of threads, you will know that each thread has its own call stack. We will cover the thread's call stack topic in the next section. The creation of a new thread is a complex operation that takes about two megabytes of memory. Coroutines use a thread pool under the hood, and only require the creation of several additional methods and classes. That is why you can consider coroutines as lightweight threads.
Let's imagine that we have a long-term operation, as shown in the following code:
class Image
fun loadImage() : Image {
Thread.sleep(3000)
return Image()
}
The loadImage function takes three seconds and returns an instance of the Image class. We also have the showImages function that takes three instances of the Image class, and looks as follows:
fun showImages(image1: Image, image2: Image, image3: Image) {
// .......
}
So, we have three independent tasks that can be executed in parallel. We can create three coroutines here, each of which will execute the loadImage function. To create a new coroutine, we can use one of the functions called a coroutine builder, such as async or launch:
val subTask1 = GlobalScope.async { loadImage() }
val subTask2 = GlobalScope.async { loadImage() }
val subTask3 = GlobalScope.async { loadImage() }
The async function returns an instance of Deferred. This class encapsulates a task that will return the result in the future. A caller function suspends when it invokes the await function of an instance of the Deferred class. This means that a thread that has a call stack with this function is not blocked, but is just suspended. The following snippet shows how this may look:
showImages(subTask1.await(), subTask2.await(), subTask3.await())
When we call the await function, we suspend invoking the current function. In addition, the showImages function will be called when all of the subtasks return the result.
The following diagram shows how these functions can be executed:
This diagram shows that three tasks can be executed almost in parallel, depending on whether the distribution of the load between cores and the showImages function is invoked when all three of the images are loaded.