Saketh's Blog

Saketh's Blog

Coroutine Jobs In Kotlin

Coroutine Jobs In Kotlin

Subscribe to my newsletter and never miss my upcoming articles

Before Getting Started I Suggest You Understand What's Actually A runBlocking() Is, Which I Have Discussed In The Previous Blog In This Series. And Make Sure That You Have Included Coroutines Dependency If It's Not Included:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'

->Whenever We Launch A Coroutine It Returns A Job. In Other Words, It Returns The Work Which We Have Assigned. Ex:- Loading Data, Network Operations, Working With UI, Some Background Stuff Etc.,

->These Job(s) || Work Which You Have Assigned In Coroutines Can be Saved In A Variable Too:

val work = GlobalScope.launch(Dispatchers.Main) {
            Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
}

-> Which May Look Like This In Your Android Studio || Other IDE Which You Use:

assginging a variable.png

Jobs:

-> Now In Case If You Want To Finish This Particular Variable's Task Primarily Rather Than Other Task(s) Then Jobs May Help You.

->Before Getting Started I Want To Clear You That Jobs Is Not A Function. We Have Multiple Suspend Functions And Other CodeBlocks Which Can Make Our Job Easy😉:

  • .join()

  • .cancel()

->Now, These Suspend Functions And Other CodeBlocks Can Be Defined As Jobs Through Coroutine Builders.

  • Let's Go In-Deep To Check What Actually These Functions Do And How Can Be Used And When These Can Be Used.

  • I'm Going To Use runBlocking() Which I Have Discussed In The Previous Blog, So That We'll Get An Idea How & When These Functions Can Be Used.

  • runBlocking() Blocks Whole Thread Until The Execution Of runBlocking() Completes.

-> If You Don't Want To Execute That Variable Primarily Or Don't Want To Execute In runBlocking(), You Can Execute That Variable In A Coroutine As Well:

.join():

As Name Suggests, Our Job||Work Can Join Some other Suspend Function Or Coroutine.

  • Whenever We Have Assign .join() Or Other Jobs In runBlocking() The Execution Of Variable Completes First And Then Back To The Normal State Of Our Code.

-> That Mean When Ever We Have Assigned .join() Or Other Jobs To A Variable Where We have Stored Our Coroutine Execution Will Primarily Completes First And Then Remaining Execution Of Other Code Block Will Be Continued. [If You Are Working With runBlocking()]

->As You Can See That I Have Added Multiple Coroutines With Individual Coroutine Builders Where I Have Assigned One Of The Coroutines In A Variable Named work To Demonstrate:

val work = GlobalScope.launch(Dispatchers.Main) {
            Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
        }
        GlobalScope.launch(Dispatchers.Default) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
        }
        GlobalScope.launch(Dispatchers.IO) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From IO Coroutine")
        }

Case1:

  • If .join() Is Executed In runBlocking():
 val work = GlobalScope.launch(Dispatchers.Main) {
            Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
        }
        GlobalScope.launch(Dispatchers.Main) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
            textView.text = "runBlocking() Execution Has Completed"
        }
        GlobalScope.launch(Dispatchers.IO) {
            delay(2000L)
            Log.d("jobs-.join()", "Running From IO Coroutine")
        }
        runBlocking {
            Log.d("jobs-.join()", "Executing From runBlocking()")
            delay(3000L)
            work.join()
            Log.d("jobs-.join()", "Executing Has Completed")
        }

->And Make Sure That You Have Implemented work.join() In runBlocking() As .join() Is A Suspend Function.

-> As You Can See In Both Logcat+Mobile Activity Our Variable Is Executed First And Then Other Coroutine Started Their Respected Work/Job Where Variable Is Included In runBlocking():

.join() with runblocking.png

join in runblock.gif

Case2:

  • If .join() Is Executed In Coroutine:
    val work = GlobalScope.launch(Dispatchers.Default) {
              Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
          }
          GlobalScope.launch(Dispatchers.Main) {
              delay(2000L)
              Log.d("jobs-.join()", "Running From ${Thread.currentThread().name}")
              textView.text = "Coroutine Execution Has Completed"
          }
          GlobalScope.launch(Dispatchers.IO) {
              Log.d("jobs-.join()", "Running From IO Coroutine")
              work.join()
              delay(2000L)
          }
    
    ->And Make Sure That You Have Implemented work.join() In Coroutine As .join() Is A Suspend Function.

-> As You Can See In Both Logcat+Mobile Activity Our Variable Is Executed Simultaneously With That Particular Coroutine In Which I Have Implemented work Variable And Then Other Coroutine Started Their Respected Work/Job As Well:

.join() with coroutine.png

join in coroutines.gif

  • And You Can Notice In Both The Cases That .join() Function Is Executed First.

->That's All About .join() Which Can Be Joined In Other Coroutine To Work Simultaneously With That Coroutine || In runBlocking() To Execute That Variable Primarily.

.cancel():

As Name Suggests, Our Job||Work Can Be Cancelled.

-> But Why Would Someone Cancel The Work🤔?

  • Let's See Those Cases:

->As You Can See That I Have Added A repeat(...){...} Block Where I Have Added Log Statements By 1 Second Delay Interval, By 5 Times.

  • That Means Execution Will Be Completed In 5 Seconds.
val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.join()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }

Case1:

  • If .cancel() Is Executed In runBlocking():

-> As I Already Mentioned .cancel() Will Cancel Our Work Immediately, So I am Delaying For 2 Seconds After That Our Work || Job Will Be Cancelled.

That Mean repeat's Block Code Will Be Executed 2 Times And Then The Whole Work Will Be Cancelled.

val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.cancel()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }
        runBlocking {
            delay(2000L)
            work.cancel()
            Log.d("jobs-.cancel()", "Work Is Cancelled")
        }

->And Make Sure That You Have Implemented work.cancel() In runBlocking() As .cancel() Is A Suspend Function.

-> As You Can See In Logcat That Our Variable Is Executed 2 Times And Then It Got Canceled Immediately:

.cancel with runBlocking().png Case2:

  • If .cancel() Is Executed In Coroutine:

-> As I Already Mentioned .cancel() Will Cancel Our Work Immediately, So I am Delaying For 2 Seconds After That Our Work || Job Will Be Cancelled.

That Mean repeat's Block Code Will Be Executed 2 Times And Then The Whole Work Will Be Cancelled.

val work = GlobalScope.launch(Dispatchers.Default) {
            repeat(5) {
                Log.d("jobs-.cancel()", "Started Execution From GlobalScope Which Is In A Variable")
                delay(1000L)
            }
        }
        GlobalScope.launch(Dispatchers.Default) {
            delay(2000L)
            work.cancel()
            Log.d("jobs-.cancel()", "Work Is Cancelled")
        }

->And Make Sure That You Have Implemented work.cancel() In Coroutine As .cancel() Is A Suspend Function.

-> As You Can See In Logcat That Our Variable Is Executed 2 Times And Then It Got Canceled Immediately:

.cancel with coroutine.png

However Cancelling A Coroutine Is Not Easy Always As I have Demonstrated And Cancelled Above, In Few Cases It May Just Crash The Whole Code As Well If You Didn't Properly Handle It. By The Way Most Of The Time, You May Execute withTimeOut(...){...} Rather Than Cancelling The Coroutine Which Makes Things More Simple Though.

->That's All About .cancel() Which Can Cancel The Execution (If You Want).

isActive:

-> With .cancel() We Can Cancel The Execution But The Job || Work Which We Have Executed To A Variable Won't Know That Job || Work Has Cancelled, To Over Come That isActive() Can Be Used With if Statement As I Have Demonstrated Below:

  • In Other Words, You Can Manually Check That Is Our Coroutine Active Or Not:
val work = GlobalScope.launch(Dispatchers.Default) {
            if (isActive) {
                repeat(5) {
                    Log.d(
                        "jobs-isActive",
                        "Coroutine Is Still Active"
                    )
                    delay(1000L)
                }
            }
        }
        runBlocking {
            delay(2000L)
            work.cancel()
            Log.d("jobs-isActive","Coroutine Has Cancelled")
        }

-> As You Can See That Once Coroutine Knows That Job Is Canceled It Will Leave The Execution.

isActive Logcat.png

-> And If You Actually Notice The Code You'll Notice That If Haven't Added work.join But The Code Is Executing As I Have Added It, How's That Possible🤔?

  • It's Because As I Have Added isActive In The Code, The Variable Continues To Work Until That Particular Variable Is Cancelled; As It Didn't Get Cancelled That Mean It's Still Active To Execute. That's The Reason Even Though I Didn't Mention work.join, Coroutine Will Work Like Charm ⚡. But Once That Variable Gets Cancelled The Variable Will No Longer Be Active.

withTimeOut(...){...}:

  • If You Are Thinking That "Is It Possible To Set A Particular Time And If That Work || Job Didn't Get Completed In That Particular Time It Should Be Cancelled Automatically" Without runBlocking().

-> And The Answer Is:

  • Yes It's Possible With withTimeOut(...){...} In Which You'll Give A Specific Time If That Job || Work Didn't Get Completed Within That Particular Time It Will Be Cancelled Automatically Without Any runBlocking() And Other Stuff:
GlobalScope.launch(Dispatchers.Default) {
            withTimeout(2000L) {
                repeat(5) {
                    Log.d("jobs-withTimeOut", "Coroutine Is Still Active")
                    delay(1000L)
                }
            }
        }

-> As You Can See That Once The Given Time Completes Coroutine Is Cancelled Automatically:

withtimeout.png

  • You Can Even Check That Is Our Coroutine Active In withTimeOut(...){...}:
    GlobalScope.launch(Dispatchers.Default) {
              withTimeout(2000L) {
                  if (isActive) {
                      repeat(5) {
                          Log.d("jobs-withTimeOut", "Coroutine Is Still Active")
                          delay(1000L)
                      }
                  }
              }
          }
    
    -> As You Can See That Once The Given Time Completes And Coroutine Becames InActive, Coroutine Is Cancelled Automatically:

withtimeout with isactive.png

Well That's All For Now🙌

Bye🤗