Skip to content

What is Structured concurrency

Devrath edited this page Jan 13, 2024 · 12 revisions

Basic meaning of concurrency

  • Structured concurrency is a way of executing the concurrent code so that proper management of resources and the flow of control is done.
  • Ability to pause the code execution and wait for all the concurrent flows to complete which can be traced to a specific ancestor to complete

In the context of co-routines

In co-routines, provides some rules for managing the concurrency and execution of concurrent tasks in a controlled and disciplined manner.

Challenges faced by other approaches to concurrency

In traditional concurrency models such as threads or callback-based systems, It is challenging to manage the lifecycle of concurrent tasks -> Structured concurrency addresses this by a properly structured and hierarchical approach to manage it.

Basic Idea

Create a scope and launch the co-routines within the scope so that when the scope is canceled, all the co-routines in it are canceled.

Main design rule of Android

  • The presentation layer should only run on the UI thread
  • Any other long-running task must be offloaded to a separate thread, Which is not in the presentation layer
  • If you look at Fragment/Activity/ViewModels you should not encounter a block of code that is running on a separate thread and all the code blocks must run on the UI thread.
  • So where to use the concurrency: We shall use it by encapsulating it in a use-case class.
  • Use-Case is also called interactors.

Structured concurrency in case of unit test

In the case of unit tests, We use the test dispatchers who enable us to achieve the concurrency synchronously by completing the execution of SUT before asserting a test case.

Code

class SimpleStructuredConcurrencyDemoVm @Inject constructor( ) : ViewModel() {

    // Create a root co-routine scope
    private val rootScope =  CoroutineScope(Dispatchers.Default)

    fun startNestedIndependentCoroutines() {
        // Launch a co-routine within the scope
        rootScope.launch {
            println("Start outer coroutine")

            // Create a nested co-routine scope
            val nestedScope =  CoroutineScope(Dispatchers.Default)

            // Launch a new co-routine within the nested scope
            nestedScope.launch {
                println("Start inner coroutine")
                delay(10000) // Observe we keep delay longer here than the outer delay
                println("End inner coroutine")
            }

            delay(5000) // Observe we have kept outer delay lesser than inner delay

            println("End outer coroutine")
        }
    }

    fun startNestedLinkedCoroutines() {
        // Launch a co-routine within the scope
        rootScope.launch {
            println("Start outer coroutine")

            // Launch a new co-routine within the nested scope
            launch {
                println("Start inner coroutine")
                delay(10000) // Observe we keep delay longer here than the outer delay
                println("End inner coroutine")
            }

            delay(5000) // Observe we have kept the outer delay lesser than inner delay

            println("End outer coroutine")
        }
    }

    fun startNestedChildrenCoroutines() {
        // Launch a co-routine within the scope
        rootScope.launch {
            println("Start outer coroutine")

            // Launch a new co-routine within the nested scope
            launch {
                println("Start inner coroutine-1")
                delay(10000) // Observe we keep delay longer here than the outer delay
                println("End inner coroutine-1")
            }.join()

            launch {
                println("Start inner coroutine-2")
                delay(10000) // Observe we keep delay longer here than the outer delay
                println("End inner coroutine-2")
            }.join()

            println("End outer coroutine")
        }
    }

    fun cancel() {
        println("User invokes cancel")
        rootScope.cancel(cause = CancellationException("Cancelled explicitly by user"))
    }


}

Observations

  • startNestedIndependentCoroutines()
    • We have started 2 coroutines here one was an outer-scoped co-routine and another was launched from an outer co-routine scope.
    • But here we pass a different scope to the inner coroutine.
    • We have placed a delay in the outer co-routine scope just for our demo.
    • When we cancel the outer scope here before the delay of outer scope is complete, Only the outer coroutine is canceled.
    • The inner co-routine continues to completion.
  • startNestedLinkedCoroutines()
    • Conditions are the same as startNestedIndependentCoroutines but the context of both outer and inner co-routine is the same.
    • if we cancel the outer scope, both outer and inner co-routines are canceled
  • startNestedChildrenCoroutines()
    • Here we used join, we can see the synchronous communication.
    • It indicates the outer scope is started and the next in the inner coroutine-1 is started and continuous to completion and only then the next inner co-routine is started and continuous to completion.
    • Finally the outer co-routine is completed.
Clone this wiki locally