Kotlin Coroutines: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 146: Line 146:
*Unconfined thread
*Unconfined thread
*Specific thread
*Specific thread
You specify the context in the coroutine builder
You specify the context in the coroutine builder.
*Unconfined
*default (not specified context ForkJoinPool.commonPool-xxx)
*coroutineContext
*Unconfined (default main thread)
*CommonPoool
*DefaultDispatcher (default ForkJoinPool.commonPool-xxx)
*newSingleThreadContext
*CommonPoool (default ForkJoinPool.commonPool-xxx)
*DefaultDispatcher
*newSingleThreadContext (default OwnThread)
*coroutineContext (default main thread)

Revision as of 23:02, 27 December 2020

Moores Law

I am doing this because of this graph
Previously there is fork/join for asynchronous but this code is far more complicated than it probably needs to be

    override fun compute(): Long {
        return if (high - low <= SEQUENTIAL_THRESHOLD) {
            (low until high)
                    .map { array[it].toLong() }
                    .sum()
        } else {
            val mid = low + (high - low) / 2
            val left = Sum(array, low, mid)
            val right = Sum(array, mid, high)
            left.fork()
            val rightAns = right.compute()
            val leftAns = left.join()
            leftAns + rightAns
        }
    }

Using the suspend approach is far more easier to read

suspend fun compute(array: IntArray, low: Int, high: Int): Long {

//    println("low: $low, high: $high  on ${Thread.currentThread().name}")

    return if (high - low <= SEQUENTIAL_THRESHOLD) {
        (low until high)
                .map { array[it].toLong() }
                .sum()
    } else {
        val mid = low + (high - low) / 2
        val left = async { compute(array, low, mid) }
        val right = compute(array, mid, high)
        return left.await() + right
    }
}

Coroutines

Introduction

There are many co-routine builders (maybe)

  • runBlocking (wait for co-routine to finish used for unit tests)
  • launch non-blocking
  • run
  • async

Co-routines are lightweight threads and you can run many more co-routines than threads. They are scheduled onto a thread so they do not necessarily run on the same thread. A delay operation does not stop the thread only the co-routine. e.g.

...
launch {
   delay(1000)
   println("world")
}

It is very important not to use blocking code in a co-routine. As above delay is non-blocking to the thread but does delay the co-routine whereas Thread.sleep(5000) blocking.

Join ( job.join() )

Fairly simple

fun main(args: Array<String>) = runBlocking {
   val job = launch {
      delay(1000)
      println("world")
   }
  job.join()
}

Cancel ( job.cancelAndJoin() )

For Cancel we do cancel and join or of course we use the cancelAndJoin(). This cancels because delay() checks for cancel.

fun main(args: Array<String>) = runBlocking {
   val job = launch {
      repeat(1000) {
      delay(100)
      println(".")
  }
  delay(100)
  job.cancel()
  job.join()
// Or
  // job.cancelAndJoin()
}

Yield ( yield() ) or isActive

We can use yield within out own code or isActive() if we want to do stuff beyond yield.

fun main(args: Array<String>) = runBlocking {
   val job = launch {
      repeat(1000) {
      yield()
      // if (!isActive) throw CancellationException()
      println(".")
  }
  delay(100)
  job.cancelAndJoin()
}

Warning We must do a return to a non local return, i.e. a return outside of our loop. The code below will not work

fun main(args: Array<String>) = runBlocking {
   val job = launch {
      repeat(1000) {
      yield()
      if (!isActive) return@repeat
      println(".")
  }
  job.cancelAndJoin()
}

Exceptions in Coroutines

We need to be careful as ever in cancelling and making sure we understand how the co-routine is torn down below demonstrate how this could be done. The run(NonCancellable) allow you to run a suspend function within your handling but be careful

...
  try {
...
  } catch(ex: CancellationException) {
    println("Cancelled: ${ex.message}")
  } finally {
    run(NonCancellable) {
       println("Forced non Cancel")
    }
  }
  delay(100)
  job.cancel(CancellationException("Because I can"))
  job.join()
}

Summary for Exceptions

  • Can be use to specify the reason why
    • job.cancel(CancellationException("why"))
  • Can specify any exception
    • Job.cancel(SomeExceptionType()_
  • Be Careful with this
    • if using launch will tear down the thread/kill application
    • Can use it with the async co-routine builder

Timeouts withTimeout() and withTimeoutWithNull()

These support timeout withTimeout() we have to wrap out co-routine with try catch. With withTimeoutWithNull() we only need to test the job for null to know if we completed.

Contexts

Introduction

Contexts provide a coroutine dispatcher to determine which thread the corountine is run on. Coroutines can run on

  • Pool thread
  • Unconfined thread
  • Specific thread

You specify the context in the coroutine builder.

  • default (not specified context ForkJoinPool.commonPool-xxx)
  • Unconfined (default main thread)
  • DefaultDispatcher (default ForkJoinPool.commonPool-xxx)
  • CommonPoool (default ForkJoinPool.commonPool-xxx)
  • newSingleThreadContext (default OwnThread)
  • coroutineContext (default main thread)