kotlinx.coroutines学习笔记

coroutines如其名,是kotlin提供一种轻量级的协程API,也可以理解为kotlin提供的异步操作API。

学习主要是阅读官方提供文档,圈下自己感觉的重点。

The main difference between [runBlocking]and [coroutineScope]is that the latter does not block the current thread while waiting for all children to complete.
runblocking和coroutinescope的主要区别在于后者在等待所有子线程完成时不会阻塞当前线程。

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println("Task from runBlocking")
    }
    
    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(500L) 
            println("Task from nested launch")
        }
    
        delay(100L)
        println("Task from coroutine scope") // This line will be printed before nested launch
    }
    
    println("Coroutine scope is over") // This line is not printed until nested launch completes
}

output:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

then returns from the main function after some delay:
延迟后从主函数返回

import kotlinx.coroutines.*

fun main() = runBlocking {
    GlobalScope.launch {
        repeat(1000) { i ->
                println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // just quit after delay    
}

output:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

Because cancellation is just an exception, all the resources are closed in a usual way. You can wrap the code with timeout in try {...} catch (e: TimeoutCancellationException) {...} block if you need to do some additional action specifically on any kind of timeout or use withTimeoutOrNull function that is similar to withTimeout, but returns null on timeout instead of throwing an exception:
withTimeoutOrNull同withTimeout用法一样,区别在于抛出异常的时候用null代替

import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
                println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
}

output:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null

A Channel is conceptually very similar to BlockingQueue. One key difference is that instead of a blocking put operation it has a suspending send, and instead of a blocking take operation it has a suspending receive.
Unlike a queue, a channel can be closed to indicate that no more elements are coming. On the receiver side it is convenient to use a regular for loop to receive elements from the channel.
概念上,通道与阻塞队列非常相似。主要关键的区别是,put操作用suspending send代替,而take操作用suspending receive代替。
跟队列不同,可以通过关闭一个通道来表示没有更多的元素。在接收端,使用标准for循环从通道接收元素会更加方便。

This way, if something goes wrong inside the code of failedConcurrentSum function and it throws an exception, all the coroutines that were launched in its scope are cancelled.

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        failedConcurrentSum()
    } catch(e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum(): Int = coroutineScope {
    val one = async { 
        try {
            delay(Long.MAX_VALUE) // Emulates very long computation
            42
        } finally {
            println("First child was cancelled")
        }
    }
    val two = async { 
        println("Second child throws an exception")
        throw ArithmeticException()
    }
        one.await() + two.await()
}

output:
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticExceptionl

There are couple of observations to make out of it.

First of all, select is biased to the first clause. When several clauses are selectable at the same time, the first one among them gets selected. Here, both channels are constantly producing strings, so a channel, being the first clause in select, wins. However, because we are using unbuffered channel, the a gets suspended from time to time on its [send] invocation and gives a chance for b to send, too.

The second observation, is that [onReceiveOrNull] gets immediately selected when the channel is already closed.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*

suspend fun selectAorB(a: ReceiveChannel, b: ReceiveChannel): String =
    select {
        a.onReceiveOrNull { value -> 
            if (value == null) 
                "Channel 'a' is closed" 
            else 
                "a -> '$value'"
        }
        b.onReceiveOrNull { value -> 
            if (value == null) 
                "Channel 'b' is closed"
            else    
                "b -> '$value'"
        }
    }
    
fun main() = runBlocking {
    val a = produce {
        repeat(4) { send("Hello $it") }
    }
    val b = produce {
        repeat(4) { send("World $it") }
    }
    repeat(8) { // print first eight results
        println(selectAorB(a, b))
    }
    coroutineContext.cancelChildren()        
}

output:
a -> 'Hello 0'
a -> 'Hello 1'
b -> 'World 0'
a -> 'Hello 2'
a -> 'Hello 3'
b -> 'World 1'
Channel 'a' is closed
Channel 'a' is closed

你可能感兴趣的:(kotlinx.coroutines学习笔记)