Kotlin-超详细协程简单易懂

协程通过替代回调(callback)来简化异步代码。

协程的执行其实是断断续续的: 执行一段, 挂起来, 再执行一段, 再挂起来, ...
每个挂起的地方是一个suspension point, 每一小段执行是一个Continuation。
协程的执行流被它的 "suspension point" 分割成了很多个 "Continuation" 。

官方示例

runBlocking {
            repeat(100000) {
                //循环100000次
                launch {
                    //开启一个协程
                    delay(1000L)
                    Log.i("log", "aaa")
                }
            }
        }

delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会挂起协程,并且只能在协程中使用。当这个协程处于等待状态时该线程会返回线程池中,当等待结束的时候,这个协程会在线程池中的空闲线程上恢复。

runBlocking并非coroutine-builder,所以它不需要CoroutineScope来调用,仅仅是能够接收一个suspend lambda而已。

launch和cancle

 val job = GlobalScope.launch(Dispatchers.Default) {
        //推荐使用默认的Dispatchers.Default上下文
        //在一个协程环境中,执行后台耗时操作
      repeat(100000) {
            delay(1000L)// delay是一个特殊的挂起函数 ,它不会造成线程阻塞,但是会挂起协程,并且只能在协程中使用。
            Log.i("log", "aaa")
      }
}
Log.i("log", "执行主线程代码")//这句代码会立即执行,因为delay不会阻塞主线程
tv.setOnClickListener {
  job.cancel()//协程中,对这个任务进行取消
}

async和await

GlobalScope.launch(Dispatchers.Default) {
     val result = async {
           netRequest()
     }
     Log.i("log", "${result.await()}")//在async方法结束的时候,就会调用await()方法
}

fun netRequest(): Int {
    return 666
}

runBlocking执行块

fun play() = runBlocking {
    GlobalScope.launch {
        // 在后台启动一个新的协程并继续
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!") // 主协程在这里会立即执行
    delay(2000L)      // 延迟 2 秒来保证 JVM 存活
}

输出结果 Hello! World!

delay和runBlocking的区别:
delay:非阻塞的函数
runBlocking:会一直阻塞到块中的代码执行完

join

fun play() = runBlocking {
    val job = GlobalScope.launch {
        // 在后台启动一个新的协程并继续
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello!")
    job.join()//等待直到子协程执行结束  主协程与后台作业的持续时间没有任何关系了
    Log.i("log", "子协程执行结束")
}

输出结果:Hello! World! 子协程执行结束

结构化的并发:我们可以在执行操作所在的指定作用域内启动协程, 而不是像通常使用线程(线程总是全局的)那样在 GlobalScope 中启动。

fun play() = runBlocking {
    launch {
        delay(1000L)
        Log.i("log", "World!")
    }
    Log.i("log", "Hello,")
}

作用域构建器

fun play() = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        Log.i("log", "Task from runBlocking")
    }

    coroutineScope { // 创建一个协程作用域,等待所有子协程执行完毕时不会阻塞当前线程,并且在所有已启动子协程执行完毕之前不会结束。r
        launch {
            delay(500L)
            Log.i("log", "Task from nested launch")
        }

        delay(100L)
        Log.i("log", "Task from coroutine scope") // 这一行会在内嵌launch之前输出
    }
    Log.i("log", "Coroutine scope is over")// 这一行在内嵌launch执行完毕后才输出
}

输出结果:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

挂起函数:使用关键字suspend

fun play() = runBlocking{
    launch {
        downLoad()
    }
    Log.i("log", "Hello,")
}

suspend fun downLoad(){
    Log.i("log", "World")
}

suspend方法只被允许在协程或另一个挂起函数中调用, 不能在协程外面调用。
suspend方法本质上, 与普通方法有较大的区别, suspend方法的本质是异步返回,在协程里面, 调用suspend方法, 异步的数据像同步一样般return了。

调用suspend方法的详细流程是:
在协程里, 如果调用了一个suspend方法, 协程就会挂起, 释放自己的执行权, 但在协程挂起之前, suspend方法内部一般会启动了另一个线程或协程, 我们暂且称之为"分支执行流"吧, 它的目的是运算得到一个数据。
当suspend方法里的“分支执行流”完成后, 就会调用系统API重新恢复协程的执行, 同时会数据返回给协程(如果有的话)。
在没有协程的世界里, 通常异步的方法都需要接受一个callback用于发布运算结果。在协程里, 所有接受callback的方法, 都可以转成不需要callback的suspend方法。
suspend方法并不总是引起协程挂起, 只有其内部的数据未准备好时才会。
需要注意的是: await是suspend方法, 但async不是, 所以它才可以在协程外面调用, async只是启动了协程, async本身不会引起协程挂起, 传给async的lambda(也就是协程体)才可能引起协程挂起。

取消
协程的取消是 协作 的。一段协程代码必须协作才能被取消。 所有 kotlinx.coroutines 中的挂起函数都是 可被取消的 。它们检查协程的取消, 并在取消时抛出 CancellationException。 然而,如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的。

runBlocking {
    val startTime = System.currentTimeMillis()

    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 50) { // 一个执行计算的循环,只是为了占用 CPU
            // 每秒打印消息两次
            if (System.currentTimeMillis() >= nextPrintTime) {
                Log.i("log", "job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 等待一段时间
    Log.i("log", "main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消一个作业并且等待它结束
    Log.i("log", "main: Now I can quit.")
}

在调用取消后, 作业仍然运行到了结束为止。

使计算代码可取消

runBlocking {
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        //isActive:当当前作业仍处于活动状态(尚未完成且尚未取消)时,返回true
        while (isActive) { //使用isActive,可以被取消的计算循环
            if (System.currentTimeMillis() >= nextPrintTime) {
                Log.i("log", "job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L)// delay a bit
    Log.i("log", "main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    Log.i("log", "main: Now I can quit")
}

输出结果:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
I'm tired of waiting!
Now I can quit

在finally中释放资源

val job = launch {
    try {
        repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } finally {
        println("job: I'm running finally")
    }
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消该作业并且等待它结束
println("main: Now I can quit.")

运行不能取消的代码块

任何尝试在 finally 块中调用挂起函数的行为都会抛出 CancellationException,因为这里持续运行的代码是可以被取消的。通常,这并不是一个问题,所有良好的关闭操作(关闭一个文件、取消一个作业、或是关闭任何一种通信通道)通常都是非阻塞的,并且不会调用任何挂起函数。然而,在真实的案例中,当你需要挂起一个被取消的协程,你可以将相应的代码包装在 withContext(NonCancellable) {……} 中,并使用 withContext

val job = launch {
    try {
        repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } finally {
        withContext(NonCancellable) {
            println("job: I'm running finally")
            delay(1000L)//如果不用NonCancellable,这个delay不会执行
            println("job: And I've just delayed for 1 sec because I'm non-cancellable")
        }
    }
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消该作业并等待它结束
println("main: Now I can quit.")

使用 withTimeout和withTimeoutOrNull操作超时

withTimeout的使用:

runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i ->
            Log.i("log","I'm sleeping $i ...")
            delay(500L)
        }
    }
}

输出结果:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
随后抛出了一个异常:
TimeoutCancellationException: Timed out waiting for 1300 ms

withTimeoutOrNull的使用:需要做一些各类使用超时的特别的额外操作,可以使用类似 withTimeout 的 withTimeoutOrNull函数,并把这些会超时的代码包装在 try {...} catch (e: TimeoutCancellationException) {...} 代码块中,而 withTimeoutOrNull通过返回 null 来进行超时操作,从而替代抛出一个异常:

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

输出结果:
I'm sleeping 0 ..
I'm sleeping 1 ..
I'm sleeping 2 ..
Result is null

通道

send和receive的使用

runBlocking {
    val channel = Channel<Int>()//实例化通道
    launch {
        for (x in 1..5) channel.send(x * x)//使用send发送消息
    }
    repeat(5) {
        Log.i("log", "${channel.receive()}")//使用receive接收消息
    }
}

使用close关闭通道:一个通道可以通过被关闭来表明没有更多的元素将会进入通道。 在接收者中可以定期的使用 for 循环来从通道中接收元素。

runBlocking {
    val channel = Channel<Int>()//实例化通道
    launch {
        for (x in 1..5) channel.send(x * x)
        channel.close()//此处关闭通道
    }
    repeat(5) {
        //这里保证所有先前发送出去的元素都在通道关闭前被接收到。
        Log.i("log", "${channel.receive()}")
    }
}

构建通道生产者
使用produce便捷的协程构建器,可以很容易的在生产者端正确工作, 并且我们使用扩展函数 consumeEach 在消费者端替代 for 循环:

fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) send(x * x)
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }//使用consumeEach代替for循环
    println("Done!")
}

管道
管道是一种一个协程在流中开始生产可能无穷多个元素的模式:

fun CoroutineScope.productNumbers() = produce<Int> {
    var x = 1
    while (true) {
        send(x++)
    }
}

并且另一个或多个协程开始消费这些流,做一些操作,并生产了一些额外的结果。 在下面的例子中,对这些数字仅仅做了平方操作:

fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
     for (x in numbers) send(x * x)
}

启动并连接整个通道:

runBlocking {
     var nunbers = productNumbers()
     var square = square(nunbers)
     for (i in 1..5) Log.i("log", "${square.receive()}")
     coroutineContext.cancelChildren()//取消子协程
}

组合挂起函数
假如有两个方法:

suspend fun doSomethingUsefulOne(): Int {
  delay(1000L) // 假设我们在这里做了一些有用的事
  return 13
}

suspend fun doSomethingUsefulTwo(): Int {
  delay(1000L) // 假设我们在这里也做了一些有用的事
  return 29
}

默认顺序调用

val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    Log.i("log", "The answer is ${one + two}")
}
Log.i("log", "Completed in $time ms")

明明是同步的写法为什么不会阻塞主线程? 对,因为suspend。
被suspend修饰的函数比普通函数多两个操作(suspend 和 resume)
suspend:暂停当前协程的执行,保存所有的局部变量
resume:从协程被暂停的地方继续执行协程。
suspend修饰的函数并不意味着运行在子线程中。

使用async并发

//使用async并发  这个比按顺序调用的快了一倍。注意:使用协程进行并发总是显式的。
val time2 = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time2 ms")

惰性启动的async

//惰性启动的async:使用一个可选的参数 start 并传值 CoroutineStart.LAZY,可以对 async 进行惰性操作。 只有当结果需
// 要被 await 或者如果一个 start 函数被调用,协程才会被启动。
val time3 = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
    one.start()
    two.start()
    Log.i("log", "The answer is ${one.await() + two.await()}")
}
Log.i("log", "Completed in $time3 ms")

使用coroutineScope实现async的结构化并发

//使用coroutineScope实现async的结构化并发
//这种情况下,如果在 concurrentSum 函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都将会被取消。
suspend fun concurrentSum(): Int = coroutineScope {
   val one = async { doSomethingUsefulOne() }
   val two = async { doSomethingUsefulTwo() }
   one.await() + two.await()
}

协程上下文与调度器
协程上下文包括了一个 协程调度器 (CoroutineDispatcher),它确定了相应的协程在执行时使用一个或多个线程。协程调度器可以将协程的执行局限在指定的线程中,调度它运行在线程池中或让它不受限的运行。
所有的协程构建器诸如 launch 和 async 接收一个可选的 CoroutineContext 参数,它可以被用来显式的为一个新协程或其它上下文元素指定一个调度器。

如果需要指定协程运行的线程,就需要指定Dispatchers :
Dispatchers.Main:Android中的主线程,可以直接操作UI。
Dispatchers.IO:针对磁盘和网络IO进行了优化,适合IO密集型的任务,比如:读写文件,操作数据库以及网络请求。
Dispatchers.Default:适合CPU密集型的任务,比如解析JSON文件,排序一个较大的list。
通过withContext()可以指定Dispatchers

启动一个协程需要CoroutineScope:
launch 启动一个协程,返回一个Job,可用来取消协程;有异常直接抛出。
async 启动一个带返回结果的协程,可以通过Deferred.await()获取结果;有异常并不会直接抛出,只会在调用 await 的时候抛出。
withContext 启动一个协程,传入CoroutineContext改变协程运行的上下文。

调度器的几种形式:

runBlocking {
      launch {
         // 运行在父协程的上下文中,即 runBlocking 主协程
         //I'm working in thread main
         Log.i("log", "main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(Dispatchers.Unconfined) {
         // 不受限的——将工作在主线程中
         //I'm working in thread DefaultDispatcher-worker-1
         Log.i("log", "Unconfined            : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(Dispatchers.Default) {
         // 将会获取默认调度器,一般使用这个默认的就可以
         // I'm working in thread main
         Log.i("log", "Default               : I'm working in thread ${Thread.currentThread().name}")
      }

      launch(newSingleThreadContext("MyOwnThread")) {
         // 将使它获得一个新的线程
         //I'm working in thread MyOwnThread
         Log.i("log", "newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
      }
}

newSingleThreadContext 为协程的运行启动了一个线程。 一个专用的线程是一种非常昂贵的资源。 在真实的应用程序中两者都必须被释放,当不再需要的时候,使用 close 函数,或存储在一个顶级变量中使它在整个应用程序中被重用。

父协程和子协程
子协程:当一个协程被其它协程在CoroutineScope中启动的时候, 它将通过CoroutineScope.coroutineContext来承袭上下文,并且这个新协程的Job将会成为父协程作业的子 作业。当一个父协程被取消的时候,所有它的子协程也会被递归的取消。

如果在 foo 里协程启动了bar 协程,那么 bar 协程必须在 foo 协程之前完成。

然而,当 GlobalScope 被用来启动一个协程时,它与作用域无关且是独立被启动的。

runBlocking {
   // 启动一个协程来处理某种传入请求(request)
   val request = launch {
   // 孵化了两个子作业, 其中一个通过 GlobalScope 启动
       GlobalScope.launch {
           Log.i("log","job1: I run in GlobalScope and execute           independently!")
           delay(1000)
           Log.i("log","job1: I am not affected by cancellation of the request")
       }
       // 另一个则承袭了父协程的上下文
       launch {
           delay(100)
           Log.i("log","job2: I am a child of the request coroutine")
           delay(1000)
           Log.i("log","job2: I will not execute this line if my parent request is cancelled")
       }
    }
    delay(500)
    request.cancel() // 取消请求(request)的执行
    delay(1000) // 延迟一秒钟来看看发生了什么
    Log.i("log","main: Who has survived request cancellation?")
}

输出结果:

job1: I run in GlobalScope and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?

父协程的职责:一个父协程总是等待所有的子协程执行结束。父协程并不显式的跟踪所有子协程的启动以及不必使用 Job.join 在最后的时候等待它们。

runBlocking {
    val request2 = launch {
        repeat(3) { i ->
            // 启动少量的子作业
            launch {
                delay((i + 1) * 200L) // 延迟 200 毫秒、400 毫秒、600 毫秒的时间
                Log.i("log2", "Coroutine $i is done")
            }
        }
        Log.i("log2", "request: I'm done and I don't explicitly join my children that are still active")
    }
    request2.join() // 等待请求的完成,包括其所有子协程,没有join,下面的语句会提前输出
    Log.i("log2", "Now processing of the request is complete")
}

输出结果:

request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete

select的使用

select 表达式可以同时等待多个挂起函数,并 选择 第一个可用的。

使用send()和onReceive()方法来实现此功能。

fun CoroutineScope.fizz() = produce<String> {
    while (true) {
        delay(300)
        send("Fizz")
    }
}

fun CoroutineScope.buzz() = produce<String> {
    while (true) {
    delay(300)
        send("Buzz")
    }
}

runBlocking { 
    val fizz = fizz()
    val buzz = buzz()
    repeat(7) {
        selelctFizzAddBuzz(fizz, buzz)
    }
    coroutineContext.cancelChildren() // 取消 fizz 和 buzz 协程           
}

通道关闭时select:select 中的 onReceive 子句在已经关闭的通道执行会发生失败,并导致相应的 select 抛出异常。我们可以使用onReceiveOrNull子句在关闭通道时执行特定操作。

suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
    select<String> {
        //<String>表示返回值为String
        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'"
        }
    }

runBlocking {
       val a = produce<String> {
            repeat(4) { send("Hello $it") }
       }
       val b = produce<String> {
            repeat(4) { send("World $it") }
       }
       repeat(8) { selectAorB(a, b) }
       coroutineContext.cancelChildren()
}

共享的可变状态和并发

actor的使用

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
            val counter = counterActor() // create the actor
            withContext(Dispatchers.Default) {
                massiveRun {
                    counter.send(IncCounter)
                }
            }
            // send a message to get a counter value from an actor
            val response = CompletableDeferred<Int>()
            counter.send(GetCounter(response))
            Log.i("log","Counter = ${response.await()}")//Counter = 10000000
            counter.close() // shutdown the actor
        }
    }
}

sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply

// This function launches a new counter actor

fun CoroutineScope.counterActor() = actor<CounterMsg> {
    var counter = 0 // actor state
    for (msg in channel) { // iterate over incoming messages
        when (msg) {
            is IncCounter -> counter++
            is GetCounter -> msg.response.complete(counter)
        }
    }
}

suspend fun massiveRun(action: suspend () -> Unit) {
    val n = 1000
    val k = 10000
    val time = measureTimeMillis {
        coroutineScope {
            repeat(n) {
                launch {
                    repeat(k) { action() }
                }
            }
        }
    }
    Log.i("log","Completed ${n * k} actions in $time ms")// Completed 10000000 actions in 81301 ms
}

Activity和协程的生命周期以及结构化并发

创建一个协程的基类,方法取消以及防止忘记

abstract class ScopedAppActivity: AppCompatActivity(),CoroutineScope by MainScope(){
    override fun onDestroy() {
        super.onDestroy()
        cancel()//在activity被销毁的时候立即取消。
    }
}

1)CoroutineScope:不建议手动实现此接口,建议使用委托实现,按照惯例,范围的上下文应该包含作业的实例,以实现结构化并发。
2)MainScope:自动提供 Dispatchers.Main 以及父级任务。
3)每个coroutine构建器(如launch,async等)都是CoroutineScope上的一个扩展
4)每个coroutine构建器(如launch、async等)和每个作用域函数(如coroutineScope、withContext等)都将自己的作用域和自己的作业实例提供给它运行的内部代码块。按照惯例,它们都要等待块内的所有协程完成,然后才能完成它们自己,从而强制执行结构化并发的原则。CoroutineScope应该在具有定义良好的生命周期的实体上实现(或用作字段),这些实体负责启动子协同程序。

子类Activity实现基类

class MainActivity22 : ScopedAppActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        asyncShowData()
    }

    fun asyncShowData() = launch {
        //Activity的job作为父结构时,这里将在UI上下文中被调用
        showIOData()
    }

    suspend fun showIOData() {
        val deferred = async(Dispatchers.IO) {
            Log.i("aaaaaaaaaaaaaaaaaa", "111111")
            // 实现
            netRequest()
        }
        withContext(Dispatchers.Main) {
            //在UI中展示数据
            val data = deferred.await()
            Log.i("aaaaaaaaaaaaaaaaaa", "222222")
            Log.i("aaaaaaaaaaaaaaaaaa", "${data}")
        }
    }

    fun netRequest(): Int {
        return 666;
    }
}

使用协程操作UI更新

class MainActivity : AppCompatActivity() {
    val channel = Channel<Int>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //使用协程更新UI以及取消协程
        setup(hello, button)
        //在 UI 上下文中使用actors
        setup2(hello, button)
    }
}

fun View.onClick(action: suspend () -> Unit) {
    setOnClickListener {
        GlobalScope.launch(Dispatchers.Main) {
            action()
        }
    }
}

//更新UI只能在主线程中进行
fun setup(hello: TextView, button: Button) {
    val job = GlobalScope.launch(Dispatchers.Main) {
        // 在主线程启动协程
        for (i in 100 downTo 1) { // 从 10 到 1 的倒计时
            hello.text = "Countdown $i ..." // 更新文本
            delay(500) // 等待半秒钟
        }
        hello.text = "Done!"
    }
}

fun setup2(hello: TextView, button: Button) {
    button.onClick2 {
        for (i in 100 downTo 1) { // 从 10 到 1 的倒计时
            hello.text = "Countdown $i ..." // 更新文本
            delay(500) // 等待半秒钟
        }
        hello.text = "Done!"
    }
}

fun View.onClick2(action: suspend (View) -> Unit) {
    // 启动一个 actor
    val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
        for (event in channel) action(event)
    }
    // 设置一个监听器来启用 actor
    setOnClickListener {
        eventActor.offer(it)
    }
}

我们在主UI上下文中启动协程,我们可以在该协程内部自如的更新UI,并同时调用就像delay这样的 挂起函数 。当delay函数的等待期间UI并不会冻结,因为它不会阻塞UI线程——它只会挂起协程。

Java中创建线程的两种方式:Thread Runnable

协程它能替换掉Handler,AsyncTask 甚至是Rxjava来优雅的解决异步问题。

kotlin协程的三种启动方式:
1)runBlocking:T
runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。
2)launch:Job
我们最常用的用于启动协程的方式,它最终返回一个Job类型的对象,这个Job类型的对象实际上是一个接口,它包涵了许多我们常用的方法。例如join()启动一个协程、cancel() 取消一个协程,该方式启动的协程任务是不会阻塞线程的
3)async/await:Deferred
async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的。
async用于启动一个异步的协程任务,await用于去得到协程任务结束时返回的结果,结果是通过一个Deferred对象返回的。

同步和异步
同步:一直等待结果。
异步:可能结果不会马上返回,而是在函数执行的某段时间后,我们通过轮询查看执行的结果,或通过函数的回调来告知我们结果。

协程内存泄漏
1)如何避免泄漏呢?这其实就是CoroutineScope 的作用,通过launch或者async启动一个协程需要指定CoroutineScope,当要取消协程的时候只需要调用CoroutineScope.cancel() ,kotlin会帮我们自动取消在这个作用域里面启动的协程。

2)结构化并发可以保证当一个作用域被取消,作用域里面的所有协程会被取消。
结构化并发保证当suspend函数返回的时候,函数里面的所有工作都已经完成。
结构化并发保证当协程出错时,协程的调用者或者他的做作用域得到通知。
由此可见,结构化并发可以保证代码更加安全,避免了协程的泄漏问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270