Kotlin 协程中runBlocking、launch、withContext、async的使用以及区别

步入Kotlin已经蛮久了,它的很多优秀的语法糖真是甜到爆炸,越来越让开发者进行最少的代码来做出更多的事情,今天我来记录了一下创建协程几个非常重要的关键方法,他们之间的区分,用好了你会体会到它的强大的。

这里做一些简单的对比:

可以在全局创建协程的方法:runBocking 和lauch 

 lauch 与 runBlocking都能在全局开启一个协程,但 lauch 是非阻塞的 而 runBlocking 是阻塞的

阻塞和非阻塞的意思是,当前运行的代码块是否会阻塞当前线程,我们看一下下面的例子:

当我们使用lauch方式启动协程的时候,看如下代码:

CoroutineScope(Dispatchers.Main).launch {
    delay(500)
    Log.e("lcs - ","1.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")
}
Log.e("lcs - ","2.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")

输出结果为:

可以看到我们协程并没有阻塞主线程,而是在启动协程之后直接执行后面的代码段,所以2.执行 会先打印出来。

我们再看看runBlocking启动协程,代码如下:

runBlocking {
    delay(500)
    Log.e("lcs - ","1.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")
}
Log.e("lcs - ","2.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")

输出结果如下:

可以看到我们执行顺序是顺序执行的,说明已经阻塞了。

大家可以看到这其中的差别了,不过后者我们很少会用到, 由于runBlocking 接收的 lambda 代表着一个 CoroutineScope,所以 runBlocking 协程体内可继续通过launch来继续创建一个协程,避免了lauch所在的线程已经运行结束而切不回来的情况。

可接收返回值得协程 withContext和async

这两种方式我们声明一个变量来接收,代码如下:

 

CoroutineScope(Dispatchers.Main).launch {
    val beginTime = System.currentTimeMillis()
    val result1 = withContext(Dispatchers.IO) {
        delay(500)
        Log.e("lcs - ", "result1.... [当前线程为:${Thread.currentThread().name}]")
        "result1"
    }
    val result2 = withContext(Dispatchers.IO) {
        delay(200)
        Log.e("lcs - ", "result2.... [当前线程为:${Thread.currentThread().name}]")
        "result2"
    }

    Log.e(
        "lcs",
        "result1 = $result1  , result2 = $result2 , 耗时 ${System.currentTimeMillis() - beginTime} ms  [当前线程为:${Thread.currentThread().name}]"
    )
}

然后我们看看打印结果是什么?

我们可以发现时间是两个任务的和,说明withContext这种方式是串行的,并且逐一返回结果,这是因为withConext是个 suspend 函数,当运行到 withConext 时所在的协程就会挂起,直到withConext执行完成后再执行下面的方法。所以withConext可以用在一个请求结果依赖另一个请求结果的这种情况,这种情况也是有不少的,有时候我们的需求就是这样的,一个页面多个结果,那我们第二个请求要依赖第一个请求的结果的时候我们就可以这样做。

说到这里我们也会有这样的需求,几个请求之间没有任何依赖关系的时候,我们怎么办?很显然这个方法就不行了,接下来async、wait就登场了,让我们看看这东西怎么实现的呢?

 

CoroutineScope(Dispatchers.Main).launch {
    val beginTime = System.currentTimeMillis()
    val result1 = async(Dispatchers.IO) {
        delay(500)
        Log.e("lcs - ", "result1.... [当前线程为:${Thread.currentThread().name}]")
        "result1"
    }
    val result2 = async(Dispatchers.IO) {
        delay(200)
        Log.e("lcs - ", "result2.... [当前线程为:${Thread.currentThread().name}]")
        "result2"
    }
    Log.e(
        "lcs",
        "result1 = ${result1.await()}  , result2 = ${result2.await()} , 耗时 ${System.currentTimeMillis() - beginTime} ms  [当前线程为:${Thread.currentThread().name}]"
    )
}

运行结果:

运行结果我们发现,时间上少了很多,说明是异步并行的两个任务,任务2要比任务1先一步执行,所以说 async 的任务都是并行执行的。但事实上有一种情况例外,我们把await()方法的调用提前到 async 的后面,代码如下:

 

CoroutineScope(Dispatchers.Main).launch {
    val beginTime = System.currentTimeMillis()
    val result1 = async(Dispatchers.IO) {
        delay(500)
        Log.e("lcs - ", "result1.... [当前线程为:${Thread.currentThread().name}]")
        "result1"
    }.await()
    val result2 = async(Dispatchers.IO) {
        delay(200)
        Log.e("lcs - ", "result2.... [当前线程为:${Thread.currentThread().name}]")
        "result2"
    }.await()
    Log.e(
        "lcs",
        "result1 = result1  , result2 = result2 , 耗时 ${System.currentTimeMillis() - beginTime} ms  [当前线程为:${Thread.currentThread().name}]"
    )
}

运行结果如下:

我们发现时间上跟withContext差不多,又回到了串行的时候的情况了,刚只是把await()的位置改了,就出现这样的结果,所以原因应该就是在await()方法身上,点进 await() 源码看一下,终于明白了是怎么一回事,原来await() 仅仅被定义为 suspend 函数,因此直接在async 后面使用 await() 就和 withContext 一样,程序运行到这里就会被挂起直到该函数执行完成才会继续执行下一个 async 。但事实上await()也不一定导致协程会被挂起,await() 只有在 async 未执行完成返回结果时,才会挂起协程。若 async 已经有结果了,await() 则直接获取其结果并赋值给变量,此时不会挂起协程

简单的总结就这样先,我也是看了别人的资料,然后自己亲自实践了一下,这样不仅有助于记忆和理解,还能够使用顺手。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章