Kotlin Coroutines: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 68: Line 68:
</syntaxhighlight>
</syntaxhighlight>


===Cancel ( job.cancelAndJoin() )===
==Cancel ( job.cancelAndJoin() )==
For Cancel we do cancel and join or of course we use the cancelAndJoin(). This cancels because delay() checks for cancel.
For Cancel we do cancel and join or of course we use the cancelAndJoin(). This cancels because delay() checks for cancel.
<syntaxhighlight lang="kotlin">
<syntaxhighlight lang="kotlin">
Line 85: Line 85:
</syntaxhighlight>
</syntaxhighlight>


===Yield ( yield() ) or isActive ===
==Yield ( yield() ) or isActive==
We can use yield within out own code or isActive() if we want to do stuff beyond yield.
We can use yield within out own code or isActive() if we want to do stuff beyond yield.
<syntaxhighlight lang="kotlin">
<syntaxhighlight lang="kotlin">
Line 111: Line 111:
}
}
</syntaxhighlight>
</syntaxhighlight>
===Exceptions in Coroutines===
==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
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
<syntaxhighlight lang="kotlin">
<syntaxhighlight lang="kotlin">

Revision as of 22:52, 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.