Composing Suspending Functions in Kotlin

Learn via video courses
Topics Covered

Overview

Composing Suspending Functions in Kotlin involves combining asynchronous functions with Kotlin's suspend functionality. It facilitates the creation of intricate asynchronous workflows by utilizing coroutine-based suspending functions. This enhances code modularity and readability while managing concurrency seamlessly.

Sequential by default

In Kotlin's coroutine framework, when you work with suspending functions, they inherently follow a "Sequential by Default" execution model. This means that when you call multiple suspend function kotlin in a sequential manner within a coroutine scope, they will be executed one after the other, adhering to the order of invocation. This behavior ensures that each suspend function kotlin completes before the next one starts.

Consider a scenario where you're building an application that requires step-by-step data processing. By leveraging the sequential nature of suspend functions kotlin, you can ensure that the processing steps are executed in the desired sequence, leading to code that closely mirrors the logical flow of the program.

This default sequential behavior eliminates the need for explicit synchronization mechanisms, such as locks or semaphores, to manage the order of execution. This simplicity and predictability in the code contribute to improved readability and maintainability.

Moreover, this characteristic aligns with the coroutine philosophy of structured concurrency, where the order and relationships between asynchronous tasks are clearly defined, leading to more robust and less error-prone code.

In cases where concurrent execution is needed, Kotlin provides various constructs, such as async and launch, to manage concurrency explicitly.

However, the "Sequential by Default" nature of suspend function kotlin provides a solid foundation for building asynchronous workflows that are intuitive and easy to reason about.

Let us consider the follwowing example,

Code:

In this code:

  • fetchUserData() and fetchRelevantInfo() are suspend functions kotlin that simulate asynchronous tasks with artificial delays.
  • runBlocking is a coroutine builder that creates a coroutine and blocks the main thread until all its child coroutines are completed.
  • Within the runBlocking scope, we call fetchUserData() to fetch user data asynchronously and then fetchRelevantInfo(userData) to process relevant information asynchronously.
  • The program will print the result once both asynchronous operations are complete, demonstrating how coroutines allow us to write asynchronous code in a more sequential and readable manner.

Output:

Concurrent using async

Concurrent using async is a technique within Kotlin's coroutine framework that facilitates parallel execution of multiple suspend functions kotlin. It addresses scenarios where independent asynchronous tasks need to be performed concurrently for improved efficiency and responsiveness.

By leveraging the async coroutine builder, developers can initiate several suspend functions kotlin simultaneously, allowing them to execute independently in parallel.

Each async call returns a deferred result, which represents a future value. These deferred results can be coordinated and awaited using the await() function, enabling the consolidation of outcomes once all tasks complete.

This approach is beneficial for tasks that can be executed in isolation without being dependent on each other's outcomes. By concurrently executing these tasks, applications can make better use of available resources and reduce overall execution time.

Let us consider the following example:

Code:

Output:

Explaination:

In this example, we aim to fetch stock prices concurrently for multiple stocks. The fetchStockPrice function simulates variable data retrieval delays, and the async coroutine builder allows these functions to execute concurrently.

Using async, we create a list of deferred tasks, where each task corresponds to fetching a stock price. We use the await() function to retrieve the results from these deferred tasks.

The final output showcases that the stock prices are fetched concurrently for all stocks, with execution time being determined by the actual data retrieval time of each API call. This approach maximizes the utilization of available resources and minimizes the time taken to complete all API calls.

Note: Please note that the actual numbers will vary due to the random nature of the data generation and the simulated delays.

Lazily started async

Lazily started async in Kotlin refers to a technique used with suspend functions kotlin and coroutines for asynchronous programming.

It involves using the async coroutine builder with the start parameter set to CoroutineStart.LAZY. This means that the coroutine computation won't start until explicitly awaited or if you call its start function.

This allows for more efficient resource utilization, as the coroutine starts only when its result is actually needed. It's particularly useful when dealing with multiple coroutines and you want to avoid unnecessary parallelism.

Code:

Output:

Explaination:

In this example, we define a suspend function fetchData that simulates fetching data asynchronously with a delay.

In the main function, we create a list of Deferred objects using the async coroutine builder with start set to CoroutineStart.LAZY.

We explicitly start the coroutine computation using the start function for each Deferred object. We then await each deferred result and print the fetched data along with the index. The total time taken to fetch all data is printed at the end.

Please note that the output timings may vary depending on system performance and load, but you'll observe that the data fetching happens sequentially, and the total time will be close to the sum of individual delays (approximately 5 seconds in this case).

Async-style functions

Async-style functions, often used in coroutine-based programming like in Kotlin, allow non-blocking execution of tasks. These functions return a Deferred result that represents a future value.

By using async coroutine builders, you can concurrently initiate multiple asynchronous tasks and later await their results using await. This enables efficient use of resources and responsiveness in applications.

Code:

Output:

Explaination:

The fetchUserData function simulates fetching user data asynchronously with a 1-second delay. In the main function, a list of user IDs is created. Each ID's data is fetched concurrently using the async builder.

The forEachIndexed loop iterates through the list of Deferred results, awaiting each result using await(), and printing the fetched user data along with the index.

Since the async operations run concurrently, the output shows user data for each ID after approximately a 1-second delay for each user. The order may vary due to concurrency.

Structured concurrency with async

Structured concurrency is a programming paradigm that ensures well-defined and controlled execution of concurrent tasks.

In the context of Kotlin coroutines, structured concurrency is achieved by using the async coroutine builder within suspend functions kotlin. This ensures that all concurrently launched coroutines are properly managed, awaited, and their lifecycle is tied to the enclosing scope.

Code:

Output:

Explaination:

The fetchUserData function is a suspend function that uses coroutineScope to ensure structured concurrency. Within the coroutineScope, an async coroutine builder is used to fetch user data asynchronously. The fetched user data is returned after being awaited using the await() function.

In the main function, a list of user IDs is created. Each ID's data is fetched concurrently using the async builder. The forEachIndexed loop iterates through the list of Deferred results, awaiting each result using await(), and printing the fetched user data along with the index.

Since structured concurrency is used, if an exception occurs within any coroutine, it will be propagated and properly managed within its scope, preventing uncontrolled behavior.

Conclusion

  • Enhanced Asynchronous Workflows: Composing suspending functions in Kotlin offers a robust solution for orchestrating complex asynchronous tasks by combining suspending functions. This leads to more organized and readable code while managing concurrency seamlessly.

  • Sequential Execution by Default: Kotlin's coroutine framework follows a "Sequential by Default" execution model for suspending functions, ensuring tasks run in the order they are invoked. This simplifies code and eliminates the need for explicit synchronization mechanisms.

  • Efficient Concurrency with Async: Utilizing the async coroutine builder enables concurrent execution of independent tasks, improving efficiency and responsiveness. Deferred results are coordinated and awaited, allowing parallel execution and resource optimization.

  • Lazily Started Async: The "Lazily started async" technique delays coroutine computation until necessary, optimizing resource utilization. This is beneficial when dealing with multiple coroutines and helps avoid unnecessary parallelism.

  • Structured Concurrency: Employing async within coroutineScope ensures structured concurrency, managing coroutine lifecycles and exceptions. This approach promotes clean and controlled concurrent programming.